Commit d9b2b2a850d0a6d6044fe2d01eff09e0754ff0c1
1 parent
276f9b23
enabled basic reading/writing of BMP files without an external library
Showing
3 changed files
with
329 additions
and
4 deletions
Show diff stats
1 | +#ifndef STIM_GRID3_H | ||
2 | +#define STIM_GRID3_H | ||
3 | + | ||
4 | +namespace stim{ | ||
5 | + | ||
6 | +template<typename T, typename F = float> | ||
7 | +class grid : public stim::grid<T, 3, F>{ | ||
8 | + | ||
9 | +public: | ||
10 | + | ||
11 | + /// Convert grid coordinates (integers) into world coordinates (F) based on the pixel spacing | ||
12 | + void grid2volume(size_t xi, size_t yi, size_t zi, F& x, F& y, F&z){ | ||
13 | + | ||
14 | + } | ||
15 | + | ||
16 | + /// Use linear interpolation to get a value from the grid at (x, y, z) in VOLUME space (based on voxel size) | ||
17 | + T lerp(F x, F y, F z){ | ||
18 | + | ||
19 | + } | ||
20 | + /// Create a resampled grid with isotropic voxel sizes | ||
21 | + grid3<T, F> resample_iso(){ | ||
22 | + | ||
23 | + //find the smallest spacing | ||
24 | + //create a new grid of the appropriate size | ||
25 | + //use linear interpolation to resample the old grid into the new grid | ||
26 | + } | ||
27 | + | ||
28 | + | ||
29 | +}; | ||
30 | +} //end namespace stim | ||
31 | + | ||
32 | +#endif | ||
0 | \ No newline at end of file | 33 | \ No newline at end of file |
1 | +#pragma once | ||
2 | + | ||
3 | +namespace stim { | ||
4 | +#pragma pack(1) | ||
5 | + typedef unsigned int DWORD; | ||
6 | + typedef unsigned short WORD; | ||
7 | + typedef signed int LONG; | ||
8 | + typedef struct tagBITMAPFILEHEADER { | ||
9 | + WORD bfType; | ||
10 | + DWORD bfSize; | ||
11 | + WORD bfReserved1; | ||
12 | + WORD bfReserved2; | ||
13 | + DWORD bfOffBits; | ||
14 | + } BITMAPFILEHEADER, *PBITMAPFILEHEADER; | ||
15 | + | ||
16 | + const unsigned int DIB_BITMAPCOREHEADER = 12; | ||
17 | + const unsigned int DIB_OS21XBITMAPHEADER = 16; | ||
18 | + const unsigned int DIB_BITMAPINFOHEADER = 40; | ||
19 | + const unsigned int DIB_BITMAPV2INFOHEADER = 52; | ||
20 | + const unsigned int DIB_BITMAPV3INFOHEADER = 56; | ||
21 | + const unsigned int DIB_OS22XBITMAPHEADER = 64; | ||
22 | + const unsigned int DIB_BITMAPV4HEADER = 108; | ||
23 | + const unsigned int DIB_BITMAPV5HEADER = 124; | ||
24 | + | ||
25 | + typedef struct tagBITMAPCOREHEADER { | ||
26 | + DWORD bcSize; | ||
27 | + WORD bcWidth; | ||
28 | + WORD bcHeight; | ||
29 | + WORD bcPlanes; | ||
30 | + WORD bcBitCount; | ||
31 | + } BITMAPCOREHEADER, *PBITMAPCOREHEADER; | ||
32 | + | ||
33 | + typedef struct tagBITMAPINFOHEADER { | ||
34 | + DWORD biSize; //40 bytes | ||
35 | + LONG biWidth; | ||
36 | + LONG biHeight; | ||
37 | + WORD biPlanes; | ||
38 | + WORD biBitCount; | ||
39 | + DWORD biCompression; | ||
40 | + DWORD biSizeImage; | ||
41 | + LONG biXPelsPerMeter; | ||
42 | + LONG biYPelsPerMeter; | ||
43 | + DWORD biClrUsed; | ||
44 | + DWORD biClrImportant; | ||
45 | + } BITMAPINFOHEADER, *PBITMAPINFOHEADER; | ||
46 | + | ||
47 | + // From FileFormat.info | ||
48 | + typedef struct { | ||
49 | + DWORD Size; /* Size of this header in bytes */ | ||
50 | + LONG Width; /* Image width in pixels */ | ||
51 | + LONG Height; /* Image height in pixels */ | ||
52 | + WORD Planes; /* Number of color planes */ | ||
53 | + WORD BitsPerPixel; /* Number of bits per pixel */ | ||
54 | + DWORD Compression; /* Compression methods used */ | ||
55 | + DWORD SizeOfBitmap; /* Size of bitmap in bytes */ | ||
56 | + LONG HorzResolution; /* Horizontal resolution in pixels per meter */ | ||
57 | + LONG VertResolution; /* Vertical resolution in pixels per meter */ | ||
58 | + DWORD ColorsUsed; /* Number of colors in the image */ | ||
59 | + DWORD ColorsImportant; /* Minimum number of important colors */ | ||
60 | + /* Fields added for Windows 4.x follow this line */ | ||
61 | + | ||
62 | + DWORD RedMask; /* Mask identifying bits of red component */ | ||
63 | + DWORD GreenMask; /* Mask identifying bits of green component */ | ||
64 | + DWORD BlueMask; /* Mask identifying bits of blue component */ | ||
65 | + DWORD AlphaMask; /* Mask identifying bits of alpha component */ | ||
66 | + DWORD CSType; /* Color space type */ | ||
67 | + LONG RedX; /* X coordinate of red endpoint */ | ||
68 | + LONG RedY; /* Y coordinate of red endpoint */ | ||
69 | + LONG RedZ; /* Z coordinate of red endpoint */ | ||
70 | + LONG GreenX; /* X coordinate of green endpoint */ | ||
71 | + LONG GreenY; /* Y coordinate of green endpoint */ | ||
72 | + LONG GreenZ; /* Z coordinate of green endpoint */ | ||
73 | + LONG BlueX; /* X coordinate of blue endpoint */ | ||
74 | + LONG BlueY; /* Y coordinate of blue endpoint */ | ||
75 | + LONG BlueZ; /* Z coordinate of blue endpoint */ | ||
76 | + DWORD GammaRed; /* Gamma red coordinate scale value */ | ||
77 | + DWORD GammaGreen; /* Gamma green coordinate scale value */ | ||
78 | + DWORD GammaBlue; /* Gamma blue coordinate scale value */ | ||
79 | + } WIN4XBITMAPHEADER; | ||
80 | + | ||
81 | + typedef struct { | ||
82 | + DWORD bV5Size; | ||
83 | + LONG bV5Width; | ||
84 | + LONG bV5Height; | ||
85 | + WORD bV5Planes; | ||
86 | + WORD bV5BitCount; | ||
87 | + DWORD bV5Compression; | ||
88 | + DWORD bV5SizeImage; | ||
89 | + LONG bV5XPelsPerMeter; | ||
90 | + LONG bV5YPelsPerMeter; | ||
91 | + DWORD bV5ClrUsed; | ||
92 | + DWORD bV5ClrImportant; | ||
93 | + DWORD bV5RedMask; | ||
94 | + DWORD bV5GreenMask; | ||
95 | + DWORD bV5BlueMask; | ||
96 | + DWORD bV5AlphaMask; | ||
97 | + DWORD bV5CSType; | ||
98 | + LONG RedX; /* X coordinate of red endpoint */ | ||
99 | + LONG RedY; /* Y coordinate of red endpoint */ | ||
100 | + LONG RedZ; /* Z coordinate of red endpoint */ | ||
101 | + LONG GreenX; /* X coordinate of green endpoint */ | ||
102 | + LONG GreenY; /* Y coordinate of green endpoint */ | ||
103 | + LONG GreenZ; /* Z coordinate of green endpoint */ | ||
104 | + LONG BlueX; /* X coordinate of blue endpoint */ | ||
105 | + LONG BlueY; /* Y coordinate of blue endpoint */ | ||
106 | + LONG BlueZ; /* Z coordinate of blue endpoint */ | ||
107 | + DWORD bV5GammaRed; | ||
108 | + DWORD bV5GammaGreen; | ||
109 | + DWORD bV5GammaBlue; | ||
110 | + DWORD bV5Intent; | ||
111 | + DWORD bV5ProfileData; | ||
112 | + DWORD bV5ProfileSize; | ||
113 | + DWORD bV5Reserved; | ||
114 | + } BITMAPV5HEADER, *PBITMAPV5HEADER; | ||
115 | + | ||
116 | + | ||
117 | + //compression methods | ||
118 | + const unsigned int BI_RGB = 0; | ||
119 | + const unsigned int BI_BITFIELDS = 3; | ||
120 | + | ||
121 | + class bmp { | ||
122 | + std::ifstream file; | ||
123 | + public: | ||
124 | + unsigned int dib_header_size; | ||
125 | + size_t bit_pos; // start position (relative to the beginning of the file) of the bitmap bits | ||
126 | + size_t total_size; //total size of the bitmap file (in bytes) | ||
127 | + size_t width; | ||
128 | + size_t height; | ||
129 | + int channels; | ||
130 | + int bits_per_pixel; | ||
131 | + unsigned int compression; | ||
132 | + | ||
133 | + size_t bytes() { | ||
134 | + return width * height * bits_per_pixel / 8; | ||
135 | + } | ||
136 | + void read_bmpFileHeader() { | ||
137 | + BITMAPFILEHEADER file_header; | ||
138 | + file.read((char*)&file_header, sizeof(BITMAPFILEHEADER)); | ||
139 | + bit_pos = file_header.bfOffBits; | ||
140 | + total_size = file_header.bfSize; | ||
141 | + } | ||
142 | + void read_bmpCoreHeader() { | ||
143 | + tagBITMAPCOREHEADER header; | ||
144 | + file.read((char*)&header, sizeof(tagBITMAPCOREHEADER)); | ||
145 | + width = header.bcWidth; | ||
146 | + height = header.bcHeight; | ||
147 | + bits_per_pixel = header.bcBitCount; | ||
148 | + compression = 0; | ||
149 | + } | ||
150 | + void read_bmpInfoHeader() { | ||
151 | + tagBITMAPINFOHEADER info_header; | ||
152 | + file.read((char*)&info_header, sizeof(tagBITMAPINFOHEADER)); | ||
153 | + width = info_header.biWidth; | ||
154 | + height = info_header.biHeight; | ||
155 | + bits_per_pixel = info_header.biBitCount; | ||
156 | + compression = info_header.biCompression; | ||
157 | + } | ||
158 | + void read_bmpV4Header() { | ||
159 | + WIN4XBITMAPHEADER header; | ||
160 | + file.read((char*)&header, sizeof(WIN4XBITMAPHEADER)); | ||
161 | + width = header.Width; | ||
162 | + height = header.Height; | ||
163 | + bits_per_pixel = header.BitsPerPixel; | ||
164 | + compression = header.Compression; | ||
165 | + } | ||
166 | + void read_bmpV5Header() { | ||
167 | + BITMAPV5HEADER header; | ||
168 | + file.read((char*)&header, sizeof(BITMAPV5HEADER)); | ||
169 | + width = header.bV5Width; | ||
170 | + height = header.bV5Height; | ||
171 | + bits_per_pixel = header.bV5BitCount; | ||
172 | + compression = header.bV5Compression; | ||
173 | + } | ||
174 | + void read_dib() { //read the bitmap DIB information header | ||
175 | + int header_pos = file.tellg(); | ||
176 | + file.read((char*)&dib_header_size, sizeof(unsigned int)); | ||
177 | + file.seekg(header_pos); | ||
178 | + switch (dib_header_size) { | ||
179 | + case DIB_BITMAPCOREHEADER: read_bmpCoreHeader(); break; | ||
180 | + case DIB_BITMAPINFOHEADER: read_bmpInfoHeader(); break; | ||
181 | + case DIB_BITMAPV4HEADER: read_bmpV4Header(); break; | ||
182 | + case DIB_BITMAPV5HEADER: read_bmpV5Header(); break; | ||
183 | + default: | ||
184 | + std::cout << "stim::bmp ERROR: this bitmap header format isn't supported" << std::endl; | ||
185 | + exit(1); | ||
186 | + } | ||
187 | + } | ||
188 | + | ||
189 | + bool open(std::string filename) { //open the bitmap file and read the header data | ||
190 | + file.open(filename, std::ifstream::binary); | ||
191 | + if (!file) { | ||
192 | + std::cout << "stim::bmp ERROR: error opening file: " << filename.c_str() << std::endl; | ||
193 | + return false; | ||
194 | + } | ||
195 | + read_bmpFileHeader(); //read the file header | ||
196 | + read_dib(); | ||
197 | + if (compression != BI_RGB) { //check for compression | ||
198 | + std::cout << "stim::bmp ERROR: this file is compressed, and compression is not supported" << std::endl; | ||
199 | + return false; | ||
200 | + } | ||
201 | + return true; | ||
202 | + } | ||
203 | + void close() { | ||
204 | + file.close(); | ||
205 | + } | ||
206 | + | ||
207 | + /// Copy the bitmap data into a pre-allocated array | ||
208 | + bool read(char* dst){ | ||
209 | + file.seekg(bit_pos); //seek to the beginning of the data array | ||
210 | + size_t row_bytes = width * bits_per_pixel / 8; //number of bytes in each row | ||
211 | + size_t padding = row_bytes % 4; //calculate the padding on disk for each row (rows must be multiples of 4) | ||
212 | + | ||
213 | + if(file){ | ||
214 | + for (size_t h = 0; h < height; h++) { //for each row in the image | ||
215 | + file.read(dst + (height - h - 1) * row_bytes, row_bytes); //read the row of image data | ||
216 | + file.seekg(padding, std::ios::cur); //seek to the end of the row on disk | ||
217 | + if (file.eof()) std::cout << "stim::bmp ERROR: array size incorrect, end of file reached while reading bitmap." << std::endl; | ||
218 | + else if (file.fail()) std::cout << "stim::bmp ERROR: reading bitmap array failed." << std::endl; | ||
219 | + else if (file.bad()) std::cout << "stim::bmp ERROR: stream integrity failed while reading bitmap array" << std::endl; | ||
220 | + } | ||
221 | + return true; | ||
222 | + } | ||
223 | + else{ | ||
224 | + std::cout<<"stim::bmp ERROR: could not read array from file."<<std::endl; | ||
225 | + return false; | ||
226 | + } | ||
227 | + } | ||
228 | + }; | ||
229 | + | ||
230 | + bool save_bmp(std::string filename, char* bits, size_t width, size_t height) { | ||
231 | + size_t bits_per_pixel = 24; | ||
232 | + size_t row_bytes = width * bits_per_pixel / 8; //number of bytes in each row | ||
233 | + size_t padding = row_bytes % 4; //calculate the padding on disk for each row (rows must be multiples of 4) | ||
234 | + | ||
235 | + tagBITMAPFILEHEADER file_header; | ||
236 | + memset(&file_header, 0, sizeof(tagBITMAPFILEHEADER)); //initialize the file header structure to zero | ||
237 | + file_header.bfOffBits = sizeof(tagBITMAPFILEHEADER) + sizeof(tagBITMAPCOREHEADER); //the offset includes both the file and DIB header | ||
238 | + size_t bytes = width * height * 3; | ||
239 | + file_header.bfSize = file_header.bfOffBits + (row_bytes + padding) * height; //calculate the size of the bitmap file | ||
240 | + file_header.bfType = 0x4D42; | ||
241 | + | ||
242 | + tagBITMAPCOREHEADER info_header; | ||
243 | + memset(&info_header, 0, sizeof(tagBITMAPCOREHEADER)); //initialize the info header to zero | ||
244 | + info_header.bcBitCount = bits_per_pixel; | ||
245 | + info_header.bcHeight = height; | ||
246 | + info_header.bcWidth = width; | ||
247 | + info_header.bcSize = sizeof(tagBITMAPCOREHEADER); | ||
248 | + info_header.bcPlanes = 1; | ||
249 | + | ||
250 | + std::ofstream outfile(filename, std::ios::binary); //open the output file for binary writing | ||
251 | + outfile.write((char*)&file_header, sizeof(tagBITMAPFILEHEADER)); //write the file header | ||
252 | + outfile.write((char*)&info_header, sizeof(tagBITMAPCOREHEADER)); //write the information header | ||
253 | + | ||
254 | + char* pad = (char*)malloc(padding); //create a buffer that will be written as padding | ||
255 | + memset(pad, 0, padding); | ||
256 | + for (size_t h = 0; h < height; h++) { | ||
257 | + outfile.write((char*)(bits + (height - h - 1) * row_bytes), row_bytes); //write the bitmap data | ||
258 | + outfile.write(pad, padding); | ||
259 | + } | ||
260 | + free(pad); | ||
261 | + return true; | ||
262 | + } | ||
263 | +} | ||
0 | \ No newline at end of file | 264 | \ No newline at end of file |
stim/image/image.h
@@ -13,6 +13,9 @@ | @@ -13,6 +13,9 @@ | ||
13 | #include <fstream> | 13 | #include <fstream> |
14 | #include <cstring> | 14 | #include <cstring> |
15 | 15 | ||
16 | +#include <stim/image/bmp.h> | ||
17 | +#include <stim/parser/filename.h> | ||
18 | + | ||
16 | namespace stim{ | 19 | namespace stim{ |
17 | /// This static class provides the STIM interface for loading, saving, and storing 2D images. | 20 | /// This static class provides the STIM interface for loading, saving, and storing 2D images. |
18 | /// Data is stored in an interleaved (BIP) format (default for saving and loading is RGB). | 21 | /// Data is stored in an interleaved (BIP) format (default for saving and loading is RGB). |
@@ -130,6 +133,17 @@ public: | @@ -130,6 +133,17 @@ public: | ||
130 | return *this; | 133 | return *this; |
131 | } | 134 | } |
132 | 135 | ||
136 | + void load_bmp(std::string filename) { | ||
137 | + stim::bmp bitmap; | ||
138 | + bitmap.open(filename); //load the bitmap and read the headers | ||
139 | + resize(bitmap.width, bitmap.height, 3); //resize the current image to match the bitmap | ||
140 | + if (!bitmap.read((char*)img)) { //read the bits from file | ||
141 | + std::cout << "stim::image ERROR: problem loading bitmap image." << std::endl; | ||
142 | + exit(1); | ||
143 | + } | ||
144 | + bitmap.close(); //close the bitmap file | ||
145 | + } | ||
146 | + | ||
133 | //save a Netpbm file | 147 | //save a Netpbm file |
134 | void load_netpbm(std::string filename) { | 148 | void load_netpbm(std::string filename) { |
135 | std::ifstream infile(filename.c_str(), std::ios::in | std::ios::binary); //open an output file | 149 | std::ifstream infile(filename.c_str(), std::ios::in | std::ios::binary); //open an output file |
@@ -226,7 +240,11 @@ public: | @@ -226,7 +240,11 @@ public: | ||
226 | if(C() == 3) //if this is a 3-color image, OpenCV uses BGR interleaving | 240 | if(C() == 3) //if this is a 3-color image, OpenCV uses BGR interleaving |
227 | from_opencv(cv_ptr, X(), Y()); | 241 | from_opencv(cv_ptr, X(), Y()); |
228 | #else | 242 | #else |
229 | - load_netpbm(filename); | 243 | + stim::filename file(filename); |
244 | + if (file.extension() == "ppm") | ||
245 | + load_netpbm(filename); | ||
246 | + else if (file.extension() == "bmp") | ||
247 | + load_bmp(filename); | ||
230 | #endif | 248 | #endif |
231 | } | 249 | } |
232 | 250 | ||
@@ -258,6 +276,10 @@ public: | @@ -258,6 +276,10 @@ public: | ||
258 | outfile.close(); | 276 | outfile.close(); |
259 | } | 277 | } |
260 | 278 | ||
279 | + void save_bmp(std::string filename) { | ||
280 | + stim::save_bmp(filename, (char*)img, width(), height()); | ||
281 | + } | ||
282 | + | ||
261 | //save a file | 283 | //save a file |
262 | void save(std::string filename){ | 284 | void save(std::string filename){ |
263 | #ifdef USING_OPENCV | 285 | #ifdef USING_OPENCV |
@@ -272,7 +294,15 @@ public: | @@ -272,7 +294,15 @@ public: | ||
272 | cv::imwrite(filename, cvImage); | 294 | cv::imwrite(filename, cvImage); |
273 | free(buffer); | 295 | free(buffer); |
274 | #else | 296 | #else |
275 | - save_netpbm(filename); | 297 | + stim::filename file(filename); |
298 | + if (file.extension() == "ppm") | ||
299 | + save_netpbm(filename); | ||
300 | + else if (file.extension() == "bmp") | ||
301 | + save_bmp(filename); | ||
302 | + else { | ||
303 | + std::cout << "stim::image ERROR: File type not supported without OpenCV. Make sure to link OpenCV and define USING_OPENCV" << std::endl; | ||
304 | + exit(1); | ||
305 | + } | ||
276 | #endif | 306 | #endif |
277 | } | 307 | } |
278 | 308 | ||
@@ -399,8 +429,8 @@ public: | @@ -399,8 +429,8 @@ public: | ||
399 | 429 | ||
400 | image<T> result = *this; //create a new image for output | 430 | image<T> result = *this; //create a new image for output |
401 | size_t N = size(); //get the number of values in the image | 431 | size_t N = size(); //get the number of values in the image |
402 | - double range = maxval - minval; //calculate the current range of the image | ||
403 | - double desired_range = high - low; //calculate the desired range of the image | 432 | + T range = maxval - minval; //calculate the current range of the image |
433 | + T desired_range = high - low; //calculate the desired range of the image | ||
404 | for (size_t n = 0; n < N; n++) { //for each element in the image | 434 | for (size_t n = 0; n < N; n++) { //for each element in the image |
405 | result.data()[n] = desired_range * (img[n] - minval) / range + low; | 435 | result.data()[n] = desired_range * (img[n] - minval) / range + low; |
406 | } | 436 | } |