Чтение bmp в C++

UPDATED: Здесь более правильный вариант программы!

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

Решение этой задачи покажется очень простым, если знать спецификацию bmp формата. Байты с цветами каждого пиксела начинаются с 54-го байта и при считывании заголовков bmp формата, указатель сместился на нужную нам позицию. Считать байт можно функцией getc(FILE *stream). Цвета каждого пиксела находятся в формате BGR(не привычный нам RGB). Остается прочитать по порядку каждый байт и записать все значения в двумерный массив.

Файл main.h

#ifndef MAIN_H_INCLUDED
#define MAIN_H_INCLUDED
 
 
typedef struct
{
    unsigned int    bfType;
    unsigned long   bfSize;
    unsigned int    bfReserved1;
    unsigned int    bfReserved2;
    unsigned long   bfOffBits;
} BITMAPFILEHEADER;
 
typedef struct
{
    unsigned int    biSize;
    int             biWidth;
    int             biHeight;
    unsigned short  biPlanes;
    unsigned short  biBitCount;
    unsigned int    biCompression;
    unsigned int    biSizeImage;
    int             biXPelsPerMeter;
    int             biYPelsPerMeter;
    unsigned int    biClrUsed;
    unsigned int    biClrImportant;
} BITMAPINFOHEADER;
 
typedef struct
{
    int   rgbBlue;
    int   rgbGreen;
    int   rgbRed;
    int   rgbReserved;
} RGBQUAD;
 
 
static unsigned short read_u16(FILE *fp);
static unsigned int   read_u32(FILE *fp);
static int            read_s32(FILE *fp);
 
#endif // MAIN_H_INCLUDEDs

Файл main.cpp

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "main.h"
 
int main()
{
    FILE * pFile = fopen("file.bmp", "rb");
 
    // считываем заголовок файла
    BITMAPFILEHEADER header __attribute__((unused));
 
    header.bfType      = read_u16(pFile);
    header.bfSize      = read_u32(pFile);
    header.bfReserved1 = read_u16(pFile);
    header.bfReserved2 = read_u16(pFile);
    header.bfOffBits   = read_u32(pFile);
 
    // считываем заголовок изображения
    BITMAPINFOHEADER bmiHeader;
 
    bmiHeader.biSize          = read_u32(pFile);
    bmiHeader.biWidth         = read_s32(pFile);
    bmiHeader.biHeight        = read_s32(pFile);
    bmiHeader.biPlanes        = read_u16(pFile);
    bmiHeader.biBitCount      = read_u16(pFile);
    bmiHeader.biCompression   = read_u32(pFile);
    bmiHeader.biSizeImage     = read_u32(pFile);
    bmiHeader.biXPelsPerMeter = read_s32(pFile);
    bmiHeader.biYPelsPerMeter = read_s32(pFile);
    bmiHeader.biClrUsed       = read_u32(pFile);
    bmiHeader.biClrImportant  = read_u32(pFile);
 
 
    RGBQUAD **rgb = new RGBQUAD*[bmiHeader.biWidth];
    for (int i = 0; i < bmiHeader.biWidth; i++) {
        rgb[i] = new RGBQUAD[bmiHeader.biHeight];
    }
 
    for (int i = 0; i < bmiHeader.biWidth; i++) {
        for (int j = 0; j < bmiHeader.biHeight; j++) {
            rgb[i][j].rgbBlue = getc(pFile);
            rgb[i][j].rgbGreen = getc(pFile);
            rgb[i][j].rgbRed = getc(pFile);
        }
 
        // пропускаем последний байт в строке
        getc(pFile);
    }
 
    // выводим результат
    for (int i = 0; i < bmiHeader.biWidth; i++) {
        for (int j = 0; j < bmiHeader.biHeight; j++) {
            printf("%d %d %d\n", rgb[i][j].rgbRed, rgb[i][j].rgbGreen, rgb[i][j].rgbBlue);
        }
        printf("\n");
    }
 
    fclose(pFile);
    return 0;
}
 
 
static unsigned short read_u16(FILE *fp)
{
    unsigned char b0, b1;
 
    b0 = getc(fp);
    b1 = getc(fp);
 
    return ((b1 << 8) | b0);
}
 
 
static unsigned int read_u32(FILE *fp)
{
    unsigned char b0, b1, b2, b3;
 
    b0 = getc(fp);
    b1 = getc(fp);
    b2 = getc(fp);
    b3 = getc(fp);
 
    return ((((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
}
 
 
static int read_s32(FILE *fp)
{
    unsigned char b0, b1, b2, b3;
 
    b0 = getc(fp);
    b1 = getc(fp);
    b2 = getc(fp);
    b3 = getc(fp);
 
    return ((int)(((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
}
  • Игорь

    Очень, очень, очень кстати!!!!!

  • Тарас

    Я не совсем понял как эту матрицу вывести в файл,например типа .txt
    если вам не трудно, не могли бы обьяснить?

    • Тарас

      все, спасибо! сам разобрался)

  • c++

    Не понимаю конструкцию return ((b1 << 8) | b0);

    • Попробую объяснить подробно.
      Мы имеем следующую функцию:

      static unsigned short read_u16(FILE *fp)
      {
          unsigned char b0, b1;
       
          b0 = getc(fp);
          b1 = getc(fp);
       
          return ((b1 << 8) | b0);
      }

      Эта функция возвращает значение типа unsigned short, это значение имеет размер в 2 байта.
      Внутри функции мы создаём 2 переменные размером в 1 байт: unsigned char b0, b1;
      Записываем в них символ: b0 = getc(fp); b1 = getc(fp);
      И теперь нам нужно возвратить оба прочитанных символа, для этого их нужно объединить.
      Допустим b1 равно 11010101, а b2 равно 01111110 — это значения двух наших однобайтовых переменных. Нам нужно возвратить 2 байта, поэтому нужно сместить b1 на 8 разрядов влево: b1 < < 8 и объединить с b0: | b0.
      Для лучшего понимания приведу небольшой код (+ см. комментарии):

      unsigned short result = 0; // result = 0000000000000000 (16 нулей)
      result = b1 << 8; // теперь result = 1101010100000000
      result |= b0; // теперь result = 1101010101111110

      Подробнее про подобные операции можно прочитать здесь: ru.wikipedia.org/wiki/Битовые_операции

      • c++

        Спасибо! Как же хорошо: помимо работы с бмп я узнал еще и о битовых операциях. Еще раз спасибо.

      • c++

        Я так понимаю,
        unsigned int это 4 байта

        • Совершенно верно.

  • Андрей

    У меня ругается на строку
    RGBQUAD rgb[bmiHeader.biWidth][bmiHeader.biHeight];
    и говорит, что
    expected constant expression
    cannot allocate an array of constant size 0

    • Андрей

      Изменил фукцию

      int _tmain(int argc, _TCHAR* argv[])
      {DISPLAY a;

      //открытие файла
      // Объявляем структуры
      unsigned int line=0;
      FILE * pFile;
      pFile = fopen(«D:\\pic\\24.bmp», «rb»);
      // считываем заголовок файла
      BITMAPFILEHEADER header;

      header.bfType = read_u16(pFile);
      header.bfSize = read_u32(pFile);
      header.bfReserved1 = read_u16(pFile);
      header.bfReserved2 = read_u16(pFile);
      header.bfOffBits = read_u32(pFile);

      // считываем заголовок изображения
      BITMAPINFOHEADER bmiHeader;

      bmiHeader.biSize = read_u32(pFile);
      bmiHeader.biWidth = read_s32(pFile);
      bmiHeader.biHeight = read_s32(pFile);
      bmiHeader.biPlanes = read_u16(pFile);
      bmiHeader.biBitCount = read_u16(pFile);
      bmiHeader.biCompression = read_u32(pFile);
      bmiHeader.biSizeImage = read_u32(pFile);
      bmiHeader.biXPelsPerMeter = read_s32(pFile);
      bmiHeader.biYPelsPerMeter = read_s32(pFile);
      bmiHeader.biClrUsed = read_u32(pFile);
      bmiHeader.biClrImportant = read_u32(pFile);

      RGBQUAD** rgb;
      rgb =(RGBQUAD**) malloc(bmiHeader.biWidth * sizeof(RGBQUAD*));//rows
      for (int i=0; i<bmiHeader.biHeight; i++)
      { rgb[i] = (RGBQUAD*) malloc (bmiHeader.biHeight*sizeof(RGBQUAD));}//collumns
      for (int i = 0; i < bmiHeader.biWidth; i++) {
      for (int j = 0; j < bmiHeader.biHeight; j++) {
      rgb[i][j].rgbBlue = getc(pFile);
      rgb[i][j].rgbGreen = getc(pFile);
      rgb[i][j].rgbRed = getc(pFile);
      }

      // пропускаем последний байт в строке
      getc(pFile);
      }

      // выводим результат
      for (int i = 0; i < bmiHeader.biWidth; i++) {
      for (int j = 0; j >line;
      return 0;
      }

      static unsigned short read_u16(FILE *fp)
      {
      unsigned char b0, b1;

      b0 = getc(fp);
      b1 = getc(fp);

      return ((b1 << 8) | b0);
      }

      теперь компилируется, запускается, но, неправильная последовательность пикселей в тестовом изображении.

      • Ваш вариант правильный, но мне кажется лучше использовать так:

        RGBQUAD **rgb = new RGBQUAD*[bmiHeader.biWidth];
         
        for (int i = 0; i < bmiHeader.biWidth; i++) {
            rgb[i] = new RGBQUAD[bmiHeader.biHeight];
        }

        Это C++ вариант, а ваш — C.
        Спасибо за информацию, пост обновил.

        • Алексей

          Кстати, да, у меня также некорректная последовательность пикселей в любых изображениях. Что-то не так считывается из файла. При этом данные двух заголовков в порядке.

          • Код, который приведён в статье, не учитывает множество стандартов формата BMP.

  • Евгений

    Ругается на строчку BITMAPFILEHEADER header __attribute__((unused));
    Говорит, что пропущена ‘;’ перед «__attribute__», но если её поставить перед «__attribute__», ошибка не исчезает. Что делать?

    • Какой компилятор?

      • IvanCHIP

        Такая же проблема… Компилятор Visual’а 2013. Да и вообще, скажите пожалуйста, что эта строчка обозначает?

        BITMAPFILEHEADER header __attribute__((unused));

        BITMAPFILEHEADER header — структура header, но а дальше — это что?..

        • Эта штука специфична для GCC, просто скрывает некоторые warning’и.

  • Дмитрий

    Спасибо за статью! Просвятите, пожалуйста, как записать информацию из массива в
    bmp

  • Елена

    Каждую из функций read_u16, read_u32 и read_s32 можно упростить, если использовать функцию чтения файла fread, в которой точно указывается сколько байт нужно прочитать.
    Например:

    static unsigned short read_u16(FILE *fp)
    {
    unsigned short N;
    fread(&N, sizeof(unsigned short), 1, fp);
    return N;
    }

  • влад

    а обратно из 2мерного массива в bmp подобным образом?

  • Сергей

    Вообще не компилится, vs 2010 express, не могли бы Вы обновить текст в шапке?

    • В GCC всё хорошо, с VS дел не имел и желания особого нет.

  • fnt


    for (int i = 0; i < bmiHeader.biWidth; i++) {
    for (int j = 0; j < bmiHeader.biHeight; j++) {
    rgb[i][j].rgbBlue = getc(pFile);
    rgb[i][j].rgbGreen = getc(pFile);
    rgb[i][j].rgbRed = getc(pFile);
    }

    // пропускаем последний байт в строке
    getc(pFile);
    }

    Здесь ведь ошибка: нужно пропускать каждый четвертый байт, а не один лишь в конце строки.

  • Vit

    Что-то как-то не так работает как ожидаю… А поэтому пара вопросов, извените если что, я новичек в C++:

    getc работает по строке (по biWidth) с файлом, а в матрицу Вы записываете по .biHeight;

    длина матрицы (?): RGBQUAD*[bmiHeader.biWidth]; RGBQUAD — 4-ре символа/байта, а считываем 3: rgbBlue/rgbGreen/rgbRed.

    Почему «// пропускаем последний байт в строке»? После каждого считывания надо пропускать 4-й байт rgbReserved; или?

    Почему в структуре RGBQUAD все цвета в int? Н/р FF переведётся сразу в 255?

    Пытаюсь сделать программку для считывания RGB значений каждого пикселя 8-битной BMP в .txt. Для пробы беру абсолютно белый .bmp с 5×5 пикселей. А программа даёт какой-то набор цифр… который нельзя ни к чему привязать….

    • Что-то как-то не так работает как ожидаю… А поэтому пара вопросов, извените если что, я новичек в C++:

      getc работает по строке (по biWidth) с файлом, а в матрицу Вы записываете по .biHeight;

      длина матрицы (?): RGBQUAD*[bmiHeader.biWidth]; RGBQUAD — 4-ре символа/байта, а считываем 3: rgbBlue/rgbGreen/rgbRed.

      Почему «// пропускаем последний байт в строке»? После каждого считывания надо пропускать 4-й байт rgbReserved; или?

      На самом деле версий стандартов для BMP масса, а этот код работает только с одной из них. С какой именно — неизвестно.

      Почему в структуре RGBQUAD все цвета в int?

      Недосмотрел, лучше поставить unsigned char.

      Н/р FF переведётся сразу в 255?

      Не понял.

      Пытаюсь сделать программку для считывания RGB значений каждого пикселя 8-битной BMP в .txt. Для пробы беру абсолютно белый .bmp с 5×5 пикселей. А программа даёт какой-то набор цифр… который нельзя ни к чему привязать….

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

      В общем, в течении пары дней, я попробую сделать правильный вариант.

    • Если ещё интересно, то я написал исправленную версию: http://ziggi.org/chtenie-bmp-v-c-versiya-2/

  • Pingback: Чтение bmp в C++, версия 2 | ziggi - blog()

  • RAY

    BITMAPFILEHEADER header __attribute__((unused)); тут ругается пишет нужна точка с запятой VS2015 что делать подскажите плиз

Перейти к верхней панели