bmp.h 9.92 KB
#ifndef STIM_BMP_H
#define STIM_BMP_H

#include <fstream>
#include <iostream>

namespace stim {
#pragma pack(1)
	typedef unsigned int DWORD;
	typedef unsigned short WORD;
	typedef signed int LONG;
	typedef struct tagBITMAPFILEHEADER {
		WORD  bfType;
		DWORD bfSize;
		WORD  bfReserved1;
		WORD  bfReserved2;
		DWORD bfOffBits;
	} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

	const unsigned int DIB_BITMAPCOREHEADER = 12;
	const unsigned int DIB_OS21XBITMAPHEADER = 16;
	const unsigned int DIB_BITMAPINFOHEADER = 40;
	const unsigned int DIB_BITMAPV2INFOHEADER = 52;
	const unsigned int DIB_BITMAPV3INFOHEADER = 56;
	const unsigned int DIB_OS22XBITMAPHEADER = 64;
	const unsigned int DIB_BITMAPV4HEADER = 108;
	const unsigned int DIB_BITMAPV5HEADER = 124;

	typedef struct tagBITMAPCOREHEADER {
		DWORD bcSize;
		WORD  bcWidth;
		WORD  bcHeight;
		WORD  bcPlanes;
		WORD  bcBitCount;
	} BITMAPCOREHEADER, *PBITMAPCOREHEADER;

	typedef struct tagBITMAPINFOHEADER {
		DWORD biSize;											//40 bytes
		LONG  biWidth;
		LONG  biHeight;
		WORD  biPlanes;
		WORD  biBitCount;
		DWORD biCompression;
		DWORD biSizeImage;
		LONG  biXPelsPerMeter;
		LONG  biYPelsPerMeter;
		DWORD biClrUsed;
		DWORD biClrImportant;
	} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

	// From FileFormat.info
	typedef struct {
		DWORD Size;            /* Size of this header in bytes */
		LONG  Width;           /* Image width in pixels */
		LONG  Height;          /* Image height in pixels */
		WORD  Planes;          /* Number of color planes */
		WORD  BitsPerPixel;    /* Number of bits per pixel */
		DWORD Compression;     /* Compression methods used */
		DWORD SizeOfBitmap;    /* Size of bitmap in bytes */
		LONG  HorzResolution;  /* Horizontal resolution in pixels per meter */
		LONG  VertResolution;  /* Vertical resolution in pixels per meter */
		DWORD ColorsUsed;      /* Number of colors in the image */
		DWORD ColorsImportant; /* Minimum number of important colors */
							   /* Fields added for Windows 4.x follow this line */

		DWORD RedMask;       /* Mask identifying bits of red component */
		DWORD GreenMask;     /* Mask identifying bits of green component */
		DWORD BlueMask;      /* Mask identifying bits of blue component */
		DWORD AlphaMask;     /* Mask identifying bits of alpha component */
		DWORD CSType;        /* Color space type */
		LONG  RedX;          /* X coordinate of red endpoint */
		LONG  RedY;          /* Y coordinate of red endpoint */
		LONG  RedZ;          /* Z coordinate of red endpoint */
		LONG  GreenX;        /* X coordinate of green endpoint */
		LONG  GreenY;        /* Y coordinate of green endpoint */
		LONG  GreenZ;        /* Z coordinate of green endpoint */
		LONG  BlueX;         /* X coordinate of blue endpoint */
		LONG  BlueY;         /* Y coordinate of blue endpoint */
		LONG  BlueZ;         /* Z coordinate of blue endpoint */
		DWORD GammaRed;      /* Gamma red coordinate scale value */
		DWORD GammaGreen;    /* Gamma green coordinate scale value */
		DWORD GammaBlue;     /* Gamma blue coordinate scale value */
	} WIN4XBITMAPHEADER;

	typedef struct {
		DWORD        bV5Size;
		LONG         bV5Width;
		LONG         bV5Height;
		WORD         bV5Planes;
		WORD         bV5BitCount;
		DWORD        bV5Compression;
		DWORD        bV5SizeImage;
		LONG         bV5XPelsPerMeter;
		LONG         bV5YPelsPerMeter;
		DWORD        bV5ClrUsed;
		DWORD        bV5ClrImportant;
		DWORD        bV5RedMask;
		DWORD        bV5GreenMask;
		DWORD        bV5BlueMask;
		DWORD        bV5AlphaMask;
		DWORD        bV5CSType;
		LONG		 RedX;          /* X coordinate of red endpoint */
		LONG		 RedY;          /* Y coordinate of red endpoint */
		LONG		 RedZ;          /* Z coordinate of red endpoint */
		LONG		 GreenX;        /* X coordinate of green endpoint */
		LONG		 GreenY;        /* Y coordinate of green endpoint */
		LONG		 GreenZ;        /* Z coordinate of green endpoint */
		LONG		 BlueX;         /* X coordinate of blue endpoint */
		LONG		 BlueY;         /* Y coordinate of blue endpoint */
		LONG		 BlueZ;         /* Z coordinate of blue endpoint */
		DWORD        bV5GammaRed;
		DWORD        bV5GammaGreen;
		DWORD        bV5GammaBlue;
		DWORD        bV5Intent;
		DWORD        bV5ProfileData;
		DWORD        bV5ProfileSize;
		DWORD        bV5Reserved;
	} BITMAPV5HEADER, *PBITMAPV5HEADER;

	
	//compression methods
	const unsigned int STIM_BI_RGB = 0;
	const unsigned int STIM_BI_BITFIELDS = 3;

	class bmp {
		std::ifstream file;
	public:
		unsigned int dib_header_size;
		size_t bit_pos;					// start position (relative to the beginning of the file) of the bitmap bits
		size_t total_size;									//total size of the bitmap file (in bytes)
		size_t width;
		size_t height;
		int channels;
		int bits_per_pixel;
		unsigned int compression;

		size_t bytes() {
			return width * height * bits_per_pixel / 8;
		}
		void read_bmpFileHeader() {
			BITMAPFILEHEADER file_header;
			file.read((char*)&file_header, sizeof(BITMAPFILEHEADER));
			bit_pos = file_header.bfOffBits;
			total_size = file_header.bfSize;
		}
		void read_bmpCoreHeader() {
			tagBITMAPCOREHEADER header;
			file.read((char*)&header, sizeof(tagBITMAPCOREHEADER));
			width = header.bcWidth;
			height = header.bcHeight;
			bits_per_pixel = header.bcBitCount;
			compression = 0;
		}
		void read_bmpInfoHeader() {
			tagBITMAPINFOHEADER info_header;
			file.read((char*)&info_header, sizeof(tagBITMAPINFOHEADER));
			width = info_header.biWidth;
			height = info_header.biHeight;
			bits_per_pixel = info_header.biBitCount;
			compression = info_header.biCompression;
		}
		void read_bmpV4Header() {
			WIN4XBITMAPHEADER header;
			file.read((char*)&header, sizeof(WIN4XBITMAPHEADER));
			width = header.Width;
			height = header.Height;
			bits_per_pixel = header.BitsPerPixel;
			compression = header.Compression;
		}
		void read_bmpV5Header() {
			BITMAPV5HEADER header;
			file.read((char*)&header, sizeof(BITMAPV5HEADER));
			width = header.bV5Width;
			height = header.bV5Height;
			bits_per_pixel = header.bV5BitCount;
			compression = header.bV5Compression;
		}
		void read_dib() {								//read the bitmap DIB information header
			std::streamoff header_pos = file.tellg();
			file.read((char*)&dib_header_size, sizeof(unsigned int));
			file.seekg(header_pos);
			switch (dib_header_size) {
			case DIB_BITMAPCOREHEADER: read_bmpCoreHeader(); break;
			case DIB_BITMAPINFOHEADER: read_bmpInfoHeader(); break;
			case DIB_BITMAPV4HEADER: read_bmpV4Header(); break;
			case DIB_BITMAPV5HEADER: read_bmpV5Header(); break;
			default:
				std::cout << "stim::bmp ERROR: this bitmap header format isn't supported" << std::endl;
				exit(1);
			}
		}

		bool open(std::string filename) {								//open the bitmap file and read the header data
			file.open(filename, std::ifstream::binary);
			if (!file) {
				std::cout << "stim::bmp ERROR: error opening file: " << filename.c_str() << std::endl;
				return false;
			}
			read_bmpFileHeader();										//read the file header
			read_dib();
			if (compression != STIM_BI_RGB) {								//check for compression
				std::cout << "stim::bmp ERROR: this file is compressed, and compression is not supported" << std::endl;
				return false;
			}
			return true;
		}
		void close() {
			file.close();
		}

		/// Copy the bitmap data into a pre-allocated array
		bool read(char* dst){
			file.seekg(bit_pos);						//seek to the beginning of the data array
			size_t row_bytes = width * bits_per_pixel / 8;	//number of bytes in each row
			size_t padding = row_bytes % 4;					//calculate the padding on disk for each row (rows must be multiples of 4)
			
			if(file){
				for (size_t h = 0; h < height; h++) {				//for each row in the image
					file.read(dst + (height - h - 1) * row_bytes, row_bytes);		//read the row of image data
					file.seekg(padding, std::ios::cur);				//seek to the end of the row on disk
					if (file.eof())	std::cout << "stim::bmp ERROR: array size incorrect, end of file reached while reading bitmap." << std::endl;
					else if (file.fail()) std::cout << "stim::bmp ERROR: reading bitmap array failed." << std::endl;
					else if (file.bad()) std::cout << "stim::bmp ERROR: stream integrity failed while reading bitmap array" << std::endl;
				}
				return true;				
			}
			else{
				std::cout<<"stim::bmp ERROR: could not read array from file."<<std::endl;
				return false;
			}
		}
	};

	bool save_bmp(std::string filename, char* bits, size_t width, size_t height) {
		size_t bits_per_pixel = 24;
		size_t row_bytes = width * bits_per_pixel / 8;	//number of bytes in each row
		size_t padding = row_bytes % 4;					//calculate the padding on disk for each row (rows must be multiples of 4)

		tagBITMAPFILEHEADER file_header;
		memset(&file_header, 0, sizeof(tagBITMAPFILEHEADER));									//initialize the file header structure to zero
		file_header.bfOffBits = sizeof(tagBITMAPFILEHEADER) + sizeof(tagBITMAPCOREHEADER);		//the offset includes both the file and DIB header
		file_header.bfSize = (stim::DWORD)(file_header.bfOffBits + (row_bytes + padding) * height);			//calculate the size of the bitmap file
		file_header.bfType = (stim::DWORD)0x4D42;

		tagBITMAPCOREHEADER info_header;
		memset(&info_header, 0, sizeof(tagBITMAPCOREHEADER));										//initialize the info header to zero
		info_header.bcBitCount = (stim::DWORD)bits_per_pixel;
		info_header.bcHeight = (stim::WORD)height;
		info_header.bcWidth = (stim::WORD)width;
		info_header.bcSize = sizeof(tagBITMAPCOREHEADER);
		info_header.bcPlanes = 1;

		std::ofstream outfile(filename, std::ios::binary);										//open the output file for binary writing
		outfile.write((char*)&file_header, sizeof(tagBITMAPFILEHEADER));						//write the file header
		outfile.write((char*)&info_header, sizeof(tagBITMAPCOREHEADER));						//write the information header
		
		char* pad = (char*)malloc(padding);										//create a buffer that will be written as padding
		memset(pad, 0, padding);
		for (size_t h = 0; h < height; h++) {
			outfile.write((char*)(bits + (height - h - 1) * row_bytes), row_bytes);									//write the bitmap data
			outfile.write(pad, padding);
		}
		free(pad);
		return true;
	}
}

#endif