gui2one
gui2one

Reputation: 190

Reading binary signed short issue in C

I am learning C as a hobbyist. As a fun project I decided to code a .hgt file reader. hgt file are earth elevation files. I found few information on this file format : https://dds.cr.usgs.gov/srtm/version2_1/Documentation/Quickstart.pdf

You can find files for the whole planet here : http://viewfinderpanoramas.org/Coverage%20map%20viewfinderpanoramas_org3.htm

but it seems to be pretty straight forward : a list of signed two bytes integers they say.

I found that two bytes integers are well represented by "signed short" type, is that right ? In my code , you will see I used int_16t ( I tried that when having issues with signed shorts) I believe they have the same range ) Anyway, I open the file , dump the data in an array, and write it to a bmp file.

At first I thought it worked great, but that was because I was viewing the result of a very low elevation part of the earth. When I tried to render some files corresponding to areas with mountains, the image below shows the issue.

Below is my code so far. I was pretty sure the issue is at the beginning, when reading the data, but I don't know anymore.

I would love some help. resulting image file

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <math.h>

int main(int argc, char *argv[]) {
    if(argc < 2) {
        printf("I need a hgt file path as a paramater\n");
        return 0;
    } else {
        const int DIM = 1201;
        FILE *fp;
        int16_t *elevation_buffer;

        elevation_buffer = malloc(sizeof(int16_t) * DIM * DIM); // 2 bytes integers

        fp = fopen(argv[1], "rb");

        /* Seek to the beginning of the file */
        fseek(fp, 0, SEEK_SET);

        /* read elevation data from HGT file */
        fread(elevation_buffer, sizeof(int16_t), DIM*DIM, fp);

        fclose(fp);       

        printf("sizeof signed short int : %d\n", sizeof(signed short int));
        printf("sizeof int16_t : %d\n", sizeof(int16_t));

        /*  creating a bmp file to visualize elevation tile*/
        int w = DIM;
        int h = DIM;

        int x,y,r,g,b;

        FILE *f;

        unsigned char *img = NULL;
        int filesize = 54 + 3 * w * h; //w is your image width, h is image height, both int

        img = (unsigned char *)malloc(3 * w * h);
        memset(img, 0, 3 * w * h);

        for (int i = 0; i < w; i++)
        {
            for (int j = 0; j < h; j++)
            {
                x = i;
                y = (h - 1) - j;
                float elevation = (elevation_buffer[x + y * w] - INT16_MIN) / (float)(INT16_MAX - INT16_MIN);
                r = (int)(elevation * 255);
                float freq = 100.0f;

                if (r > 255) {
                    r = 255;
                } else if(r < 0) {
                    r = 0;
                }
                g = r;
                b = r;
                img[(x + y * w) * 3 + 2] = (unsigned char)(r);
                img[(x + y * w) * 3 + 1] = (unsigned char)(g);
                img[(x + y * w) * 3 + 0] = (unsigned char)(b);
            }
        }

        unsigned char bmpfileheader[14] = {'B', 'M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0};
        unsigned char bmpinfoheader[40] = {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0};
        unsigned char bmppad[3] = {0, 0, 0};

        bmpfileheader[2] = (unsigned char)(filesize);
        bmpfileheader[3] = (unsigned char)(filesize >> 8);
        bmpfileheader[4] = (unsigned char)(filesize >> 16);
        bmpfileheader[5] = (unsigned char)(filesize >> 24);

        bmpinfoheader[4] = (unsigned char)(w);
        bmpinfoheader[5] = (unsigned char)(w >> 8);
        bmpinfoheader[6] = (unsigned char)(w >> 16);
        bmpinfoheader[7] = (unsigned char)(w >> 24);
        bmpinfoheader[8] = (unsigned char)(h);
        bmpinfoheader[9] = (unsigned char)(h >> 8);
        bmpinfoheader[10] = (unsigned char)(h >> 16);
        bmpinfoheader[11] = (unsigned char)(h >> 24);

        f = fopen("img.bmp", "wb");
        fwrite(bmpfileheader, 1, 14, f);
        fwrite(bmpinfoheader, 1, 40, f);
        for (int i = 0; i < h; i++)
        {
            fwrite(img + (w * (h - i - 1) * 3), 3, w, f);
            fwrite(bmppad, 1, (4 - (w * 3) % 4) % 4, f);
        }

        free(img);
        free(elevation_buffer);
        fclose(f);

        return 0;
    }
}

Upvotes: 1

Views: 160

Answers (1)

grek40
grek40

Reputation: 13458

You should account for the byte order... your input file format is specified to contain bytes in big endian byte order.

As a simple fix, you could just check your byteorder and reverse the read in data as necessary. The answer https://stackoverflow.com/a/8571139/5265292 explains a way how to detect byte order on your system.

// ...

fread(elevation_buffer, sizeof(int16_t), DIM*DIM, fp);

fclose(fp);       

int byteOrderCheck = 1;
if (*(char *)&byteOrderCheck == 1)
{
    // need to revert byte order for little endian
    for (int i = 0; i < DIM*DIM; ++i)
    {
        elevation_buffer[i] = (int16_t)reverse_byte_order((uint16_t)elevation_buffer[i]);
    }
}

// ...

with function reverse_byte_order as

uint16_t reverse_byte_order(uint16_t num)
{
    return ((num & 0xff) << 8) | (num >> 8);
}

Note this is untested, you may need to change some details.

Upvotes: 1

Related Questions