Write RGB values to a BITMAP image file - c++

I am currently working on imaging processing using arrays to store R,G,B values from a 24 bit BITMAP image of width 120 and height 100 pixels.
Visual Studio 2010 is being used.
I have currently extracted the individual R,G,B values into three separate2D arrays from the 24 bit bitmap (it is assumed correct as the correct R,G,B values have been written to a text file with the right pixel count as well).
These individual R,G,B values need to be restored back into an array (either 1D or 2D), which is then written to an image file. The output should be identical to the original image.
I have tried the following but the output is currently incorrect (same width, height and memory size but colouring is incorrect).
Appreciate your guidance and feedback.
#include <iostream>
#include <fstream>
#include <windows.h>
#include <WinGDI.h>
unsigned char** Allocate2DArray(int w, int h)
{
unsigned char ** buffer = new unsigned char * [h]; // allocate the rows
unsigned char * memory_pool = new unsigned char [w*h]; // allocate memory pool
for (int i = 0; i < h; ++i)
{
buffer[i] = memory_pool; // point row pointer
memory_pool += w; // go to next row in memory pool
}
return buffer;
}
void DeAllocate2DArray(unsigned char** buffer)
{
delete [] buffer[0]; // delete the memory pool
delete [] buffer; // delete the row pointers
}
using namespace std;
int main()
{
const int width = 120;
const int height = 100;
int bytesPerPixel = 3;
unsigned char m_cHeaderData[54];
unsigned char** m_cImageData = new unsigned char* [height];
for( int i = 0; i <height; i++)
{
m_cImageData[i] = new unsigned char [width*bytesPerPixel];
}
ifstream* m_pInFile;
m_pInFile = new ifstream;
m_pInFile->open("image.bmp", ios::in | ios::binary);
m_pInFile->seekg(0, ios::beg);
m_pInFile->read(reinterpret_cast<char*>(m_cHeaderData), 54);
for(int i = 0; i <height; i++)
{
m_pInFile->read(reinterpret_cast<char*>(m_cImageData[i]), width*bytesPerPixel);
}
m_pInFile->close();
// Declare a pointer of the type you want.
// This will point to the 1D array
unsigned char* array_1D;
array_1D = new unsigned char[height*width*bytesPerPixel];
if(array_1D == NULL) return 0; // return if memory not allocated
// Copy contents from the existing 2D array
int offset = 0;
for(int j=0; j<height; j++) // traverse height (or rows)
{
offset = width * bytesPerPixel* j;
for(int i=0; i<width*bytesPerPixel; i++) // traverse width
{
array_1D[offset + i] = m_cImageData[j][i];
// update value at current (i, j)
}
}
// Declare three 2D arrays to store R,G, and B planes of image.
unsigned char**arrayR_2D, **arrayG_2D, **arrayB_2D;
arrayR_2D = Allocate2DArray(width, height);
arrayG_2D = Allocate2DArray(width, height);
arrayB_2D = Allocate2DArray(width, height);
// return if memory not allocated
if(arrayR_2D == NULL || arrayG_2D == NULL || arrayB_2D == NULL) return 0;
// Extract R,G,B planes from the existing composite 1D array
ofstream RGBdata2D;
RGBdata2D.open("RGBdata2D.txt");
int pixelCount = 0;
int offsetx = 0;
int counter = 0;
for(int j=0; j<height; j++) // traverse height (or rows)
{
offsetx = width * j * bytesPerPixel;
for(int i=0; i<width*bytesPerPixel; i+=bytesPerPixel) // width
{
arrayB_2D[j][counter] = array_1D[offsetx + i+0];
arrayG_2D[j][counter] = array_1D[offsetx + i+1];
arrayR_2D[j][counter] = array_1D[offsetx + i+2];
RGBdata2D<<"B: "<< (int)arrayB_2D[j][counter] << " G: " << (int)arrayG_2D[j][counter] << " R: " << (int)arrayR_2D[j][counter]<< endl;
pixelCount++;
++counter;
}
counter = 0;
}
RGBdata2D<<"count of pixels: "<< pixelCount << endl;
RGBdata2D.close();
//put RGB from 2D array contents back into a 1D array
offset = 0;
counter = 0;
for(int j=0; j<height; j++) // traverse height (or rows)
{
offset = width * bytesPerPixel * j;
for(int i=0; i<width*bytesPerPixel; i+=bytesPerPixel) // width
{
array_1D[offset + i+0] = arrayB_2D[j][counter++];
array_1D[offset + i+1] = arrayG_2D[j][counter++];
array_1D[offset + i+2] = arrayR_2D[j][counter++];
}
counter = 0;
}
ofstream* m_pOutFileRGB;
m_pOutFileRGB = new ofstream;
m_pOutFileRGB->open("imageCopyRGB.bmp", ios::out | ios::trunc | ios::binary);
m_pOutFileRGB->write(reinterpret_cast<char*>(m_cHeaderData), 54);
for(int i = 0; i <height; i++)
{
m_pOutFileRGB->write(reinterpret_cast<char*>(array_1D), width*bytesPerPixel);
}
m_pOutFileRGB->close();
// After complete usage, delete the memory dynamically allocated
DeAllocate2DArray(arrayR_2D);
DeAllocate2DArray(arrayG_2D);
DeAllocate2DArray(arrayB_2D);
// After complete usage, delete the memory dynamically allocated
delete[] array_1D; //delete the pointer to pointer
for(int i = 0; i <height; i++)
{
delete[] m_cImageData[i];
}
delete[] m_cImageData;
system("pause");
return 0;
}

I didn't test by myself, but at this point
for(int i=0; i<width*bytesPerPixel; i+=bytesPerPixel) // width
{
array_1D[offset + i+0] = arrayB_2D[j][counter++];
array_1D[offset + i+1] = arrayG_2D[j][counter++];
array_1D[offset + i+2] = arrayR_2D[j][counter++];
}
You inclement counter too many times, and it may lead to incorrect result.
Instead, try this:
for(int i=0; i<width*bytesPerPixel; i+=bytesPerPixel) // width
{
array_1D[offset + i+0] = arrayB_2D[j][counter];
array_1D[offset + i+1] = arrayG_2D[j][counter];
array_1D[offset + i+2] = arrayR_2D[j][counter];
counter++;
}

Related

opencv slicing of a vector Mat

I am new with OpenCV. I am working on Visual Studio 2017 and use the plugin Image Watch to see Mat file of openCV.
What I've done:
I have to read a binary file to get 1000 images (256*320 pixels uint16 so 2 octets by pixel) in an array of double. After this, I wanted to see with Image Watch my data to be sure all is okay. So I convert the first image into a uchar on 8 bit to visualise it. I add my code (most part don't read it, just go to the end) :
#include "stdafx.h"
#include <iostream>
#include "stdio.h"
#include <fstream>
#include <stdint.h>
#include "windows.h"
#include <opencv2/core/core.hpp> // cv::Mat
#include <math.h>
#include <vector>
using namespace std;
using namespace cv;
template<class T>
T my_ntoh_little(unsigned char* buf) {
const auto s = sizeof(T);
T value = 0;
for (unsigned i = 0; i < s; i++)
value |= buf[i] << CHAR_BIT * i;
return value;
}
int main()
{
ifstream is("Filename", ifstream::binary);
if (is) {
// Reading size of the file and initialising variables
is.seekg(0, is.end);
int length = is.tellg();
int main_header_size = 3000;
int frame_header_size = 1000;
int width = 320, height = 256, count_frames = 1000;
int buffer_image = width * height * 2;
unsigned char *data_char = new unsigned char[length]; // Variable which will contains all the data
// Initializing 3D array for stocking all images
double ***data;
data = new double**[count_frames];
for (unsigned i = 0; i < count_frames; i++) {
data[i] = new double*[height];
for (unsigned j = 0; j < height; j++)
data[i][j] = new double[width];
}
// Reading the file once
is.seekg(0, is.beg);
is.read(reinterpret_cast<char*>(data_char), length);
// Convert pixel by pixel uchar into uint16 (using pointer on data_char)
int indice, minid = 65536.0, maxid = 0.0;
for (unsigned count = 0; count < count_frames; count++) {
// Initialize pointer address
indice = main_header_size + count * (frame_header_size + buffer_image) + frame_header_size;
for (unsigned i = 0; i < height; i++) {
for (unsigned j = 0; j < width; j++) {
data[count][i][j] = my_ntoh_little<uint16_t>(data_char + indice);
// Search for min/max for normalize after
if (data[count][i][j] < minid and count == 0)
minid = data[count][i][j];
if (data[count][i][j] > maxid and count == 0)
maxid = data[count][i][j];
// Updating pointer to next pixel
indice += 2;
}
}
}
// Get back first image, normalize between 0-255, cast into uchar to the future Mat object
uchar *dataImRGB = new uchar[width * height * 3];
int image_display = 900;
int pixel_norm;
for (unsigned i = 0; i < height; i++) {
for (unsigned j = 0; j < width; j++) {
pixel_norm = round((data[image_display][i][j] - double(minid)) / double(maxid - minid) * 255);
dataImRGB[i * 320 * 3 + 3 * j] = static_cast<uchar>(pixel_norm);
dataImRGB[i * 320 * 3 + 3 * j + 1] = static_cast<uchar>(pixel_norm);
dataImRGB[i * 320 * 3 + 3 * j + 2] = static_cast<uchar>(pixel_norm);
}
}
// Create Mat object (it is imageRGB8 I can see on Image watch)
Mat imageRGB8 = Mat(width, height, CV_8UC3, dataImRGB);
// Creating a list of Map and add first Mat
vector<Mat> listImages;
listImages.push_back(imageRGB8);
// -----------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------
// Future : directly keep the uchar read in the original file and import it on a Mat object
// But how to get the pixel at (0,0) of the first Mat on the vector ?
// -----------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------
// De-Allocate memory to prevent memory leak
for (int i = 0; i < count_frames; ++i) {
for (int j = 0; j < height; ++j)
delete[] data[i][j];
delete[] data[i];
}
delete[] data;
}
return 0;
}
Where I am stuck:
I don't know how to work with this vector, how to manipulate the data. For example, if i want to do the mean of all images, so the mean of all Mat objects in the vector, how to do this ? Or just how to get the first pixel of the third image in the vector ? These examples have for aim to explain me the slicing with such type of data because I know how it works with vector of double, but not with openCv object.
Thank you in advance for any help/advice.
Assuming that you have got all of your images properly packed into your image list you can do the following:
This will get the mean of all images in your list:
cv::Scalar meansum(0.0f,0.0f,0.0f);
size_t length = listImages.size();
for (size_t i = 0; i < length; i++){
//mu == mean of current image
cv::Scalar mu = cv::mean(listImages[i]);
meansum += mu;
}
float means[3] = { meansum[0] / length, meansum[1] / length, meansum[2] / length };
std::cout << "Means " << means[0] << " " << means[1] << " " << means[2] << std::endl;
To get the first pixel in your third image you can use the at() method or a row pointer. (Row pointers are faster, but don't have any guards against accessing out of bounds memory locations.)
Mat third_image = list_images[2];
//using at()
uchar first_pixel_blue_value = third_image.at<uchar>(0,0,0);
std::cout<<(int)first_pixel_blue_value<<std::endl;
//using row pointer
uchar* row = third_image.ptr<uchar>(0); //pointer to row 0
std::cout<<"blue: " <<(int)row[0];
std::cout<<" green: "<<(int)row[1];
std::cout<<" red: " <<(int)row[2];
More info can be found here:
https://docs.opencv.org/3.1.0/d2/de8/group__core__array.html (under functions)
and here:
https://docs.opencv.org/trunk/d3/d63/classcv_1_1Mat.html

How to pass 2D char array to function?

I am working on a board game and have a 2d char array for board in my main:
char board[*size][*size];
for(int i = 0; i < *size; i++) {
for(int j = 0; j < *size; j++) {
board[i][j] = ".";
}
}
I want to use this in my function named playerOneMove(?), change some of its elements and than bring back to main again to use it in playerTwoMove(?)
I can do this with 1D integer arrays but i couldn't make this work. I just want to learn the method, not full code.
The best way to learn is by looking at code.
The below code passes a 2D array. Study it.
#include <iostream>
#include <cstdio>
using namespace std;
// Returns a pointer to a newly created 2d array the array2D has size [height x width]
int** create2DArray(unsigned height, unsigned width){
int** array2D = 0;
array2D = new int*[height];
for (int h = 0; h < height; h++){
array2D[h] = new int[width];
for (int w = 0; w < width; w++){
// fill in some initial values
// (filling in zeros would be more logic, but this is just for the example)
array2D[h][w] = w + width * h;
}
}
return array2D;
}
int main(){
printf("Creating a 2D array2D\n");
printf("\n");
int height = 15;
int width = 10;
int** my2DArray = create2DArray(height, width);
printf("Array sized [%i,%i] created.\n\n", height, width);
// print contents of the array2D
printf("Array contents: \n");
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++)
{
printf("%i,", my2DArray[h][w]);
}
printf("\n");
}
// important: clean up memory
printf("\n");
printf("Cleaning up memory...\n");
for ( h = 0; h < height; h++){
delete [] my2DArray[h];
}
delete [] my2DArray;
my2DArray = 0;
printf("Ready.\n");
return 0;
}
Here's just math formulas for converting any kind of 2d array (width = height OR width != height) where x, y - indexes of 2d array; index - index of 1d array.
That's for base 1 - first 2d element has index 11 (x=1, y=1).
Guess you may implement it wherever you wish.
2D to 1D
index = width * (x-1) + y
1D to 2D
x = (index / width) + 1
y = ((index - 1) % width) + 1
For base 0 - 1st element indexes x=0, y=0
2D to 1D
index = width * x + y
1D to 2D
x = index / width
y = (index - 1) % width

memset fuction does not work in my c++ dynamic array initialization

This some parts of my opencv image processing codes.In it, I generate two dynamic arrays to store the total numbers of black points per col/row in binary image.
Here are the codes:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat srcImg = imread("oura.bmp");
width = srcImg.cols - 2;
height = srcImg.rows - 2;
Mat srcGrey;
Mat srcRoi(srcImg, Rect(1, 1, width, height));
cvtColor(srcRoi, srcGrey, COLOR_BGR2GRAY);
int thresh = 42;
int maxval = 255;
threshold(srcGrey, srcRoiBina, thresh, maxval, THRESH_BINARY);
int *count_cols = new int[width] ();
int *count_rows = new int[height] ();
for (int i = 0; i < width; i++)
{
cout << count_cols[i] << endl;
}
for (int i = 0; i < height; i++)
{
uchar *data = srcRoiBina.ptr<uchar>(i);
for (int j = 0; j < width; j++)
{
if (data[j] == 0)
{
count_cols[j]++;
count_rows[i]++;
}
}
}
delete[] count_cols;
delete[] count_rows;
return 0;
}
My question is that: if I use the follow codes
int *count_cols = new int[width];
int *count_rows = new int[height];
memset(count_cols, 0, sizeof(count_cols));
memset(count_rows, 0, sizeof(count_rows));
for (int i = 0; i < width; i++)
{
cout << count_cols[i] << endl;
}
to replace the corresponding codes below, why the dynamic arrays can not be initialized to zero? It seems that the memset does not work.
Platform: Visual Stdio 2013 + opencv 3.0.0
Could you please help me?
Additionally, the original image oura.bmp is 2592*1944.Thus the length of the dynamic array count_cols is 2590(ie, 2592-2). Is there some potential problems?
count_cols is of type int*, so sizeof(count_cols) will be 8 (64bit) or 4 (32bit). You'll want to use sizeof(int) * width instead (and similarly for rows).
sizeof(count_rows) is returning the size of the pointer, not the size of the array.
Use height * sizeof(int) instead. Same applies for the columns too.

LibQREncode qrcode to BMP

I'm creating a qrcode with the library qrencode.h
This creation is working nice but how would one output the qrcode to a BMP file within c++?
At this very moment i have this code:
const char* szSourceSring = QRCODE_TEXT;
unsigned int unWidth, x, y, l, n, unWidthAdjusted, unDataBytes;
unsigned char* pRGBData, *pSourceData, *pDestData;
QRcode* pQRC;
FILE* f;
if (pQRC = QRcode_encodeString(szSourceSring, 4, QR_ECLEVEL_H, QR_MODE_8, 1))
{
unWidth = pQRC->width;
unWidthAdjusted = unWidth * OUT_FILE_PIXEL_PRESCALER * 3;
if (unWidthAdjusted % 4)
unWidthAdjusted = (unWidthAdjusted / 4 + 1) * 4;
unDataBytes = unWidthAdjusted * unWidth * OUT_FILE_PIXEL_PRESCALER;
// Allocate pixels buffer
if (!(pRGBData = (unsigned char*)malloc(unDataBytes)))
{
printf("Out of memory");
}
// Preset to white
memset(pRGBData, 0xff, unDataBytes);
// Prepare bmp headers
BITMAPFILEHEADER kFileHeader;
kFileHeader.bfType = 0x4D42; // "BM"
kFileHeader.bfSize = sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER) +
unDataBytes;
kFileHeader.bfReserved1 = 0;
kFileHeader.bfReserved2 = 0;
kFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER kInfoHeader;
kInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
kInfoHeader.biWidth = unWidth * OUT_FILE_PIXEL_PRESCALER;
kInfoHeader.biHeight = -((int)unWidth * OUT_FILE_PIXEL_PRESCALER);
kInfoHeader.biPlanes = 1;
kInfoHeader.biBitCount = 24;
kInfoHeader.biCompression = BI_RGB;
kInfoHeader.biSizeImage = 0;
kInfoHeader.biXPelsPerMeter = 0;
kInfoHeader.biYPelsPerMeter = 0;
kInfoHeader.biClrUsed = 0;
kInfoHeader.biClrImportant = 0;
// Convert QrCode bits to bmp pixels
pSourceData = pQRC->data;
for(y = 0; y < unWidth; y++)
{
pDestData = pRGBData + unWidthAdjusted * y * OUT_FILE_PIXEL_PRESCALER;
for(x = 0; x < unWidth; x++)
{
if (*pSourceData & 1)
{
for(l = 0; l < OUT_FILE_PIXEL_PRESCALER; l++)
{
for(n = 0; n < OUT_FILE_PIXEL_PRESCALER; n++)
{
*(pDestData + n * 3 + unWidthAdjusted * l) = PIXEL_COLOR_B;
*(pDestData + 1 + n * 3 + unWidthAdjusted * l) = PIXEL_COLOR_G;
*(pDestData + 2 + n * 3 + unWidthAdjusted * l) = PIXEL_COLOR_R;
}
}
}
pDestData += 3 * OUT_FILE_PIXEL_PRESCALER;
pSourceData++;
}
}
// Output the bmp file
/*if (((f = fopen(OUT_FILE, "r")) != NULL))
{*/
f = fopen(OUT_FILE, "wb");
fwrite(&kFileHeader, sizeof(BITMAPFILEHEADER), 14, f);
fwrite(&kInfoHeader, sizeof(BITMAPINFOHEADER), 40, f);
fwrite(pRGBData, sizeof(unsigned char), unDataBytes, f);
fclose(f);
/* }
else
{
printf("Unable to open file");
}
*/
// Free data
free(pRGBData);
QRcode_free(pQRC);
}
else
{
printf("NULL returned");
}
But somehow this creates a BMP with corrupt headers. Whenever i'm opening the bmp file it says:
"BMP Image has unsupported header size"
What am i doing wrong?
And is it possible to save to png instead of BMP?
I have access to the libPNG library
Here is a code example which dumps a 24 bpp bmp file created from a QR-Code. The error you see is probably not caused by the QR-Code library, but rather something in the bmp file code.
The bmp file created by this example works fine with the image viewer packaged with my Windows 8.1. If you also do not see the error, you could check for differences in each binary output to pinpoint the problem. If you want.
This question is tagged "C++" and "C++11", so this example uses the C++ std library for file output, and doesn't use malloc. (But almost equally bad -- I use new and delete in some container code, where a std::vector member is preferred...don't tell anyone). Also, this example writes each piece of data directly to the file, instead of using a file-sized intermediate buffer, like pDestData.
#include <iostream>
#include <fstream>
// A fake (or "somewhat limited") QR Code data container
struct Qrc {
int dimsize; // the width and height
unsigned char* data; // buffer which contains the elements
Qrc() {
static const unsigned int bin[] = { // encodes an important secret message
0xfc8b7d7f,0xa801a83,0xd6e54d76,0xaa9eb2ed,0x43ed05db,0xb8786837,0x55555fe0,
0x5a4c807f,0xcf315c00,0x6e8019ce,0xc7819e0d,0xd4857ba8,0x4ac5e347,0xf6f349ba,
0xd433ccdd,0x2998361e,0x4453fab3,0x526d9085,0x81f38924,0xb4da0811,0x84b3131a,
0x9639915e,0x3b74a4ff,0x42aa0c11,0x4127be16,0x1f4350,0xff620296,0xad54de1,
0xd38c2272,0xa3f76155,0x5366a7ab,0x9bdd2257,0x300d5520,0x85842e7f,0 };
dimsize = 33;
data = new unsigned char[dimsize * dimsize];
auto p = data;
auto endp = p + dimsize * dimsize;
for(unsigned int b : bin) {
for(int i=0; i<32; ++i) {
if(p == endp) break;
*(p++) = b & (1 << i) ? 255 : 0;
} } }
Qrc(const Qrc&) = delete;
Qrc& operator = (const Qrc&) = delete;
~Qrc() { delete [] data; }
};
struct BIH { // a private definition of BITMAPINFOHEADER
unsigned int sz;
int width, height;
unsigned short planes;
short bits;
unsigned int compress, szimage;
int xppm, yppm;
unsigned int clrused, clrimp;
};
void SaveBmp(const char* filename, const Qrc& qrc) {
// Asker's Qrc struct delivered as a pointer, from a C API, but this example doesn't mimic that.
std::ofstream ofs(filename, std::ios_base::out | std::ios_base::binary);
if(!ofs) {
std::cout << "Writing " << filename << " failed\n";
return;
}
const int side_len = qrc.dimsize; // width and height of the (square) QR Code
const int pixel_side_len = 4; // QRC element's size in the bmp image (in pixels)
const int bmp_line_bytes = side_len * pixel_side_len * 3;
const int bmp_line_pad_bytes = (4 - bmp_line_bytes % 4) % 4; // bmp line data padding size
const int bmp_data_size = side_len * (bmp_line_bytes + bmp_line_pad_bytes);
BIH bih = { sizeof(bih) };
bih.width = side_len * pixel_side_len; // element count * element size
bih.height = -side_len * pixel_side_len; // negative height => data begins at top of image
bih.planes = 1;
bih.bits = 24;
const int header_size = sizeof(bih) + 14; // size of the bmp file header
const int filesize = header_size + bmp_data_size; // size of the whole file
ofs.write("BM", 2);
ofs.write(reinterpret_cast<const char*>(&filesize), 4);
ofs.write("\0\0\0\0", 4); // 2x 16-bit reserved fields
ofs.write(reinterpret_cast<const char*>(&header_size), 4);
ofs.write(reinterpret_cast<const char*>(&bih), sizeof(bih));
// pixel colors, as Blue, Green, Red char-valued triples
// the terminating null also makes these usable as 32bpp BGRA values, with Alpha always 0.
static const char fg_color[] = "\0\0\0";
static const char bg_color[] = "\xff\xff\xff";
auto pd = qrc.data;
// send pixel data directly to the bmp file
// QRC elements are expanded into squares
// whose sides are "pixel_side_len" in length.
for(int y=0; y<side_len; ++y) {
for(int j=0; j<pixel_side_len; ++j) {
auto pdj = pd;
for(int x=0; x<side_len; ++x) {
for(int i=0; i<pixel_side_len; ++i) {
// *pdj will be 0 or 255 (from "fake" Qrc)
// Using "*pdj & 1" here, just to match asker's code
// without knowing why this was done.
ofs.write(*pdj & 1 ? fg_color : bg_color, 3);
}
++pdj;
}
if(bmp_line_pad_bytes) {
ofs.write("\0\0\0", bmp_line_pad_bytes);
}
}
pd += side_len;
}
}
int main() {
SaveBmp("MyQrCode.bmp", Qrc());
}

How to speed up vector initialization c++

I had a previous question about a stack overflow error and switch to vectors for my arrays of objects. That question can be referenced here if needed: How to get rid of stack overflow error
My current question is however, how do I speed up the initialization of the vectors. My current method currently takes ~15 seconds. Using arrays instead of vectors it took like a second with a size of arrays small enough that didn't throw the stack overflow error.
Here is how I am initializing it:
in main.cpp I initialize my dungeon object:
dungeon = Dungeon(0, &textureHandler, MIN_X, MAX_Y);
in my dungeon(...) constructor, I initialize my 5x5 vector of rooms and call loadDungeon:
Dungeon::Dungeon(int dungeonID, TextureHandler* textureHandler, int topLeftX, int topLeftY)
{
currentRoomRow = 0;
currentRoomCol = 0;
for (int r = 0; r < MAX_RM_ROWS; ++r)
{
rooms.push_back(vector<Room>());
for (int c = 0; c < MAX_RM_COLS; ++c)
{
rooms[r].push_back(Room());
}
}
loadDungeon(dungeonID, textureHandler, topLeftX, topLeftY);
}
my Room constructor populates my 30x50 vector of cells (so I can set them up in the loadDungeon function):
Room::Room()
{
for (int r = 0; r < MAX_ROWS; ++r)
{
cells.push_back(vector<Cell>());
for (int c = 0; c < MAX_COLS; ++c)
{
cells[r].push_back(Cell());
}
}
}
My default cell constructor is simple and isn't doing much but I'll post it anyway:
Cell::Cell()
{
x = 0;
y = 0;
width = 16;
height = 16;
solid = false;
texCoords.push_back(0);
texCoords.push_back(0);
texCoords.push_back(1);
texCoords.push_back(0);
texCoords.push_back(1);
texCoords.push_back(1);
texCoords.push_back(0);
texCoords.push_back(1);
}
And lastly my loadDungeon() function will set up the cells. Eventually this will read from a file and load the cells up but for now I would like to optimize this a bit if possible.
void Dungeon::loadDungeon(int dungeonID, TextureHandler* textureHandler, int topLeftX, int topLeftY)
{
int startX = topLeftX + (textureHandler->getSpriteWidth()/2);
int startY = topLeftY - (textureHandler->getSpriteHeight()/2);
int xOffset = 0;
int yOffset = 0;
for (int r = 0; r < MAX_RM_ROWS; ++r)
{
for (int c = 0; c < MAX_RM_COLS; ++c)
{
for (int cellRow = 0; cellRow < rooms[r][c].getMaxRows(); ++cellRow)
{
xOffset = 0;
for (int cellCol = 0; cellCol < rooms[r][c].getMaxCols(); ++cellCol)
{
rooms[r][c].setupCell(cellRow, cellCol, startX + xOffset, startY - yOffset, textureHandler->getSpriteWidth(), textureHandler->getSpriteHeight(), false, textureHandler->getSpriteTexCoords("grass"));
xOffset += textureHandler->getSpriteWidth();
}
yOffset += textureHandler->getSpriteHeight();
}
}
}
currentDungeon = dungeonID;
currentRoomRow = 0;
currentRoomCol = 0;
}
So how can I speed this up so it doesn't take ~15 seconds to load up every time. I feel like it shouldn't take 15 seconds to load a simple 2D game.
SOLUTION
Well my solution was to use std::vector::reserve call (rooms.reserve in my code and it ended up working well. I changed my function Dungeon::loadDungeon to Dungeon::loadDefaultDungeon because it now loads off a save file.
Anyway here is the code (I got it down to about 4-5 seconds from ~15+ seconds in debug mode):
Dungeon::Dungeon()
{
rooms.reserve(MAX_RM_ROWS * MAX_RM_COLS);
currentDungeon = 0;
currentRoomRow = 0;
currentRoomCol = 0;
}
void Dungeon::loadDefaultDungeon(TextureHandler* textureHandler, int topLeftX, int topLeftY)
{
int startX = topLeftX + (textureHandler->getSpriteWidth()/2);
int startY = topLeftY - (textureHandler->getSpriteHeight()/2);
int xOffset = 0;
int yOffset = 0;
cerr << "Loading default dungeon..." << endl;
for (int roomRow = 0; roomRow < MAX_RM_ROWS; ++roomRow)
{
for (int roomCol = 0; roomCol < MAX_RM_COLS; ++roomCol)
{
rooms.push_back(Room());
int curRoom = roomRow * MAX_RM_COLS + roomCol;
for (int cellRow = 0; cellRow < rooms[curRoom].getMaxRows(); ++cellRow)
{
for (int cellCol = 0; cellCol < rooms[curRoom].getMaxCols(); ++cellCol)
{
rooms[curRoom].setupCell(cellRow, cellCol, startX + xOffset, startY - yOffset, textureHandler->getSpriteWidth(), textureHandler->getSpriteHeight(), false, textureHandler->getSpriteTexCoords("default"), "default");
xOffset += textureHandler->getSpriteWidth();
}
yOffset += textureHandler->getSpriteHeight();
xOffset = 0;
}
cerr << " room " << curRoom << " complete" << endl;
}
}
cerr << "default dungeon loaded" << endl;
}
Room::Room()
{
cells.reserve(MAX_ROWS * MAX_COLS);
for (int r = 0; r < MAX_ROWS; ++r)
{
for (int c = 0; c < MAX_COLS; ++c)
{
cells.push_back(Cell());
}
}
}
void Room::setupCell(int row, int col, float x, float y, float width, float height, bool solid, /*std::array<float, 8>*/ vector<float> texCoords, string texName)
{
cells[row * MAX_COLS + col].setup(x, y, width, height, solid, texCoords, texName);
}
void Cell::setup(float x, float y, float width, float height, bool solid, /*std::array<float,8>*/ vector<float> t, string texName)
{
this->x = x;
this->y = y;
this->width = width;
this->height = height;
this->solid = solid;
for (int i = 0; i < t.size(); ++i)
this->texCoords.push_back(t[i]);
this->texName = texName;
}
It seems wasteful to have so many dynamic allocations. You can get away with one single allocation by flattening out your vector and accessing it in strides:
std::vector<Room> rooms;
rooms.resize(MAX_RM_ROWS * MAX_RM_COLS);
for (unsigned int i = 0; i != MAX_RM_ROWS; ++i)
{
for (unsigned int j = 0; j != MAX_RM_COLS; ++j)
{
Room & r = rooms[i * MAX_RM_COLS + j];
// use `r` ^^^^^^^^^^^^^^^^^^^-----<< strides!
}
}
Note how resize is performed exactly once, incurring only one single allocation, as well as default-constructing each element. If you'd rather construct each element specifically, use rooms.reserve(MAX_RM_ROWS * MAX_RM_COLS); instead and populate the vector in the loop.
You may also wish to profile with rows and columns swapped and see which is faster.
Since it seems that your vectors have their size defined at compile time, if you can use C++11, you may consider using std::array instead of std::vector. std::array cannot be resized and lacks many of the operations in std::vector, but is much more lightweight and it seems a good fit for what you are doing.
As an example, you could declare cells as:
#include <array>
/* ... */
std::array<std::array<Cell, MAX_COLS>, MAX_ROWS> cells;
UPDATE: since a locally defined std::array allocates its internal array on the stack, the OP will experience a stack overflow due to the considerably large size of the arrays. Still, it is possible to use an std::array (and its benefits compared to using std::vector), by allocating the array on the heap. That can be done by doing something like:
typedef std::array<std::array<Cell, MAX_COLS>, MAX_ROWS> Map;
Map* cells;
/* ... */
cells = new Map();
Even better, smart pointers can be used:
#include <memory>
/* ... */
std::unique_ptr<Map> cells;
cells = std::unique_ptr(new Map());