Создать простое растровое изображение в C (без внешних библиотек) - PullRequest
0 голосов
/ 29 апреля 2018

В учебных целях я хочу создать одно растровое изображение 2x2 с использованием языка программирования C без внешних библиотек (таких как SDL).

Я прочитал, что мне нужно создать заголовок для файла растрового изображения, но не смог понять, как реализовать его в коде и почему заголовок bmp имеет эти параметры. Я не могу найти какой-либо четкий учебник о том, как я мог бы сделать это с помощью C.

Не могли бы вы дать мне несколько советов о том, как реализовать это на простом документированном примере? Я использую Linux.

1 Ответ

0 голосов
/ 30 апреля 2018

почему заголовок bmp имеет эти параметры

Пиксельные данные в растровом изображении по сути являются одномерным массивом байтов. Эти данные невозможно интерпретировать без информации заголовка, которая показывает ширину, высоту, количество битов и другую информацию о растровом изображении.

Существуют разные типы растровых изображений, в том числе разный формат палитры и непалитры 16, 24 или 32 бита, а также разные типы в некоторых из этих групп.

Заголовок также включает в себя другую информацию, которая существует по историческим причинам. Это мало что значит для изучения C. Вы просто должны это принять.

Информация заголовка представлена ​​в двух структурах BITMAPFILEHEADER и BITMAPINFO Это немного сложнее, поскольку предполагается, что структуры упакованы.

Данные пикселей зависят от формата растрового изображения. Существует не один формат растрового изображения, как упоминалось ранее. В приведенном ниже примере показано 24-битное растровое изображение.

Другая странность состоит в том, что ширина каждой строки всегда должна быть кратна 4 байтам. Это называется обивка. Также строки растрового изображения начинаются снизу, а не сверху. Это объясняется в других ресурсах

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    //width, height, and bitcount are the key factors:
    int32_t width = 2;
    int32_t height = 2;
    uint16_t bitcount = 24;//<- 24-bit bitmap

    //take padding in to account
    int width_in_bytes = ((width * bitcount + 31) / 32) * 4;

    //total image size in bytes, not including header
    uint32_t imagesize = width_in_bytes * height;

    //this value is always 40, it's the sizeof(BITMAPINFOHEADER)
    const uint32_t biSize = 40;

    //bitmap bits start after headerfile, 
    //this is sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
    const uint32_t bfOffBits = 54; 

    //total file size:
    uint32_t filesize = 54 + imagesize;

    //number of planes is usually 1
    const uint16_t biPlanes = 1;

    //create header:
    //copy to buffer instead of BITMAPFILEHEADER and BITMAPINFOHEADER
    //to avoid problems with structure packing
    unsigned char header[54] = { 0 };
    memcpy(header, "BM", 2);
    memcpy(header + 2 , &filesize, 4);
    memcpy(header + 10, &bfOffBits, 4);
    memcpy(header + 14, &biSize, 4);
    memcpy(header + 18, &width, 4);
    memcpy(header + 22, &height, 4);
    memcpy(header + 26, &biPlanes, 2);
    memcpy(header + 28, &bitcount, 2);
    memcpy(header + 34, &imagesize, 4);

    //prepare pixel data:
    unsigned char* buf = malloc(imagesize);
    for(int row = height - 1; row >= 0; row--)
    {
        for(int col = 0; col < width; col++)
        {
            buf[row * width_in_bytes + col * 3 + 0] = 255;//blue
            buf[row * width_in_bytes + col * 3 + 1] = 0;//green
            buf[row * width_in_bytes + col * 3 + 2] = 0;//red
        }
    }

    FILE *fout = fopen("test.bmp", "wb");
    fwrite(header, 1, 54, fout);
    fwrite((char*)buf, 1, imagesize, fout);
    fclose(fout);
    free(buf);

    return 0;
}

Вы можете переписать этот код, используя структуры. В этой структуре кода упаковано #pragma pack(push, 1), которое зависит от компилятора. Используйте эту версию, если директива #pragma работает.

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

#pragma pack(push, 1)
struct my_BITMAPFILEHEADER {
    uint16_t bfType;
    uint32_t bfSize;
    uint16_t bfReserved1;
    uint16_t bfReserved2;
    uint32_t bfOffBits;
};

struct my_BITMAPINFOHEADER {
    uint32_t biSize;
    int32_t  biWidth;
    int32_t  biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int32_t  biXPelsPerMeter;
    int32_t  biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
};
#pragma pack(pop)

int main(void)
{
    if(sizeof(struct my_BITMAPFILEHEADER) != 14 &&
        sizeof(struct my_BITMAPINFOHEADER) != 40)
    {
        printf("bitmap structures not packed properly\n");
        return 0;
    }

    //only width and height can be changed in this code:
    int width = 2;
    int height = 2;

    int bitcount = 24;//<- 24-bit bitmap
    int width_in_bytes = ((width * bitcount + 31) / 32) * 4;    //for padding
    uint32_t imagesize = width_in_bytes * height;   //total image size

    struct my_BITMAPFILEHEADER filehdr = { 0 };
    struct my_BITMAPINFOHEADER infohdr = { 0 };

    memcpy(&filehdr, "BM", 2);//bitmap signature
    filehdr.bfSize = 54 + imagesize;//total file size
    filehdr.bfOffBits = 54; //sizeof(filehdr) + sizeof(infohdr)

    infohdr.biSize = 40; //sizeof(infohdr)
    infohdr.biPlanes = 1; //number of planes is usually 1
    infohdr.biWidth = width;
    infohdr.biHeight = height;
    infohdr.biBitCount = bitcount;
    infohdr.biSizeImage = imagesize;

    //prepare pixel data:
    unsigned char* buf = malloc(imagesize);
    for(int row = height - 1; row >= 0; row--)
    {
        for(int col = 0; col < width; col++)
        {
            buf[row * width_in_bytes + col * 3 + 0] = 255;//blue
            buf[row * width_in_bytes + col * 3 + 1] = 0;//red
            buf[row * width_in_bytes + col * 3 + 2] = 0;//green
        }
    }

    FILE *fout = fopen("test.bmp", "wb");
    fwrite(&filehdr, sizeof(filehdr), 1, fout);
    fwrite(&infohdr, sizeof(infohdr), 1, fout);
    fwrite((char*)buf, 1, imagesize, fout);
    fclose(fout);
    free(buf);

    return 0;
}

Первая версия использовала unsigned char header[54] для достижения того же самого. См размер каждого данных и их относительное положение в памяти. Структура имеет длину 14 байтов. bfType начинается с 0 и имеет длину 2 байта (16 бит). bfSize начинается с позиции 2 и имеет длину 4 байта ...

struct my_BITMAPFILEHEADER {
    uint16_t bfType; //starts at 0, 2 bytes long
    uint32_t bfSize; //starts at 2, 4 bytes long
    uint16_t bfReserved1; //starts at 6, 2 bytes long
    uint16_t bfReserved2; //starts at 8, 2 bytes long
    uint32_t bfOffBits; //starts at 10, 4 bytes long
};

Тогда у вас есть следующая структура длиной 40 байт:

struct my_BITMAPINFOHEADER {
    uint32_t biSize;//starts at 14
    int32_t  biWidth;//starts at 18
    int32_t  biHeight;//starts at 22
    uint16_t biPlanes;//starts at 26
    uint16_t biBitCount;//starts at 28
    uint32_t biCompression;//starts at 30
    uint32_t biSizeImage;//starts at 34
    int32_t  biXPelsPerMeter;
    int32_t  biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
};

biSize начинается с 0. Но предыдущая структура была 14 байтов. Итак, biSzie начинается с 14. Это должно раскрыть тайну чисел, использованных в предыдущей версии для копирования целых чисел в header

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...