Include .png in executable

Is there a way to bundle a PNG image (or any other file) into the executable itself, instead of having to distribute 2 separate files? I'd like to use that image as a kind of background of a window. I know that this isn't really a programming question but any help would still be appreciated.

I am running Linux with GCC and wxWidgets installed.
The way I do it is pretty ghetto, but it works:

I whipped up a program which reads a binary file and converts it to a C++ style header file:

1
2
3
4
5
6
const size_t WHATEVER_FILE_SIZE = 0x1234;

const uint8_t WHATEVER_FILE[] = {
0x01,0x02
...
};


Just #include that file somewhere (probably only do it from one source file) and instead of reading from a file, read from that memory buffer. wx provides a way to do that easily (wxMemoryInputStream I think -- something like that)
One word of caution: depending on the size of the file, using this technique may raise both binary size and compilation time considerably.

Also, the array is probably best left as global and defined inside a .cpp, but declared in headers, so it will only need to be compiled once. Example:
1
2
3
4
5
//definition (in a single .cpp):
//Note: uint8_t is not standard.
const char file[]={/*...*/};
//definition (in every file that uses it or in a single .h):
extern const char file[];
Last edited on
OK, thank you!
I just finished making the program that creates header files from binaries using ifstream and get() (thanks Disch!), and everything works fine. I compiled a basic hello world application, used the program and generated an array, put it in wxMemoryInputStream and wrote it to a wxFileOutputStream to my folder, so theoretically I should have made an identical copy of the hello world program. However when I tried to run it, there was an error that just said
$ ./hello
Killed


I used:
$ cmp ./hello ./Desktop/asdf
./hello ./Desktop/asdf differ: byte 25, line 1


I opened up a hex editor and found that everywhere the original had 0x80, the new one has 0xFF. What went wrong? (I can provide the source code for the header file generator if you want, but I doubt that's the problem -- I'm suspecting ifstream::get() is not working)
If I had to guess, I'd say that, in the program that generates the array, you either a) opened the file as text instead of binary, or b) used a signed type.
Post the source and I'll be able to tell you more precisely.
Well, I tried opening the file as a binary instead, but it still doesn't work... Here's the source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// bin2h.cpp

#include "bin2h.h"

BinHeader::BinHeader(string outputLoc)
{
    if (outputLoc != "") OpenHeader(outputLoc);
    else isInit = false;
}

bool BinHeader::OpenHeader(string outputLoc)
{
    if (isInit) fHeader.close();
    fHeader.open(outputLoc.c_str(), ios::binary | ios::in);
    return (isInit = fHeader.is_open());
}

bool BinHeader::AddFile(string fileLoc, string arrName)
{
    if (isInit) {
        if (CheckArrName(arrName)) {
            ifstream fFile(fileLoc.c_str());
            if (fFile.is_open()) {
                int i = 0;
                char chr;
                fHeader << "const char " << arrName << "[]={";
                fFile.get(chr);
                if (fFile.good())
                    while (true) {
                        fHeader << CharHex(chr);
                        i++;
                        fFile.get(chr);
                        if (!fFile.good()) break;
                        else fHeader << ",";
                    }
                fHeader << "};\nconst long " << arrName << "_len=" << i << ";" << endl;
                binList[0].push_back(arrName);
                binList[1].push_back(fileLoc);
                return true;
            }
        }
    }
    return false;
}

bool BinHeader::CheckArrName(string arrName)
{
    return (CheckArrNameValid(arrName) && find(binList[0].begin(), binList[0].end(), arrName.c_str()) == binList[0].end() && find(binList[0].begin(), binList[0].end(), (arrName + "_len").c_str()) == binList[0].end());
}

void BinHeader::CloseHeader()
{
    if (isInit) {
        fHeader.close();
        isInit = false;
    }
}

BinHeader::~BinHeader()
{
    CloseHeader();
}

string CharHex(char chr)
{
    char tmp[5];
    snprintf(tmp, 5, "0x%02X, ", chr);
    return string(tmp);
}

bool CheckArrNameValid(string arrName)
{
    return (isalnum(arrName.at(0)) && arrName.substr(1).find_first_not_of(VALID_CHARS) == string::npos);
}

int main(int argc, char* argv[])
{
    BinHeader bh;
    string hfl, tmp;
    vector<string> vf[2];
    ostringstream stmp;
    do {
        cout << "Create header file at: ";
        getline(cin, hfl);
    } while (hfl == "");
    while (true) {
        cout << "Include file: ";
        getline(cin, tmp);
        if (!(tmp == "")) {
            vf[0].push_back(tmp);
            cout << "Array name: ";
            getline(cin, tmp);
            if (!bh.CheckArrName(tmp)) {
                cout << "That name is invalid or has been taken." << endl;
                vf[0].pop_back();
            }
            else vf[1].push_back(tmp);
        }
        cout << "Add another file? (y/N) ";
        getline(cin, tmp);
        if (tmp == "" || toupper(tmp.at(0)) == 'N') break;
    }
    cout << "==PROGRESS==\nCreating header file...";
    if (!bh.OpenHeader(hfl)) {
        cout << "\nERROR: Could not create header file." << endl;
        return 1;
    }
    for (int i = 0; i < vf[0].size(); i++) {
        cout << "\nAdding file '" << vf[0].at(i) << "' ...";
        stmp << "bin" << i;
        if (!bh.AddFile(vf[0].at(i), (vf[1].at(i) == "" ? stmp.str().c_str() : vf[1].at(i))))
            cout << "\nERROR: Could not read file '" << vf[0].at(i) << "'.";
        stmp.seekp(0);
    }
    cout << endl;
    return 0;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// bin2h.h
#include <cstdio>
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include <cctype>

using namespace std;

class BinHeader {
    ofstream fHeader;
    bool isInit;
    vector<string> binList[2];
public:
    BinHeader(string outputLoc = "");
    bool OpenHeader(string outputLoc);
    bool AddFile(string fileLoc, string arrName = "");
    bool CheckArrName(string arrName);
    void CloseHeader();
    ~BinHeader();
};

string CharHex(char chr);

bool CheckArrNameValid(string arrName);
#define VALID_CHARS        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" 


Also any improvements, bugs spotted, coding style feedback etc. would be welcome.
Last edited on
Okay, see, this is completely fucked up.

bin2h.cpp:
Line 7: You're ignoring the return value of OpenHeader(). The file may not have opened.
Line 14: You're opening an std::ofstream with the std::in flag. You're also opening as binary, which doesn't really make sense, since you want to write a C++ header, and those are always text. I meant you needed to open the input file as binary. I didn't think I had to make it explicit I wasn't talking about the output file.
Line 22: This is the file you should be opening as binary.

It seems to me like you made this program overly complicated. All it really needed to do was ask for an input file, an output file, and an array name, then write to the file accordingly. There was no need to use classes, and there definitely was no need to check that the array name had not already been used. Since you were going to be the only user of the program, you made it unnecessarily robust (yes, there's such a thing).
I suppose it is a bit over-complicated...

Btw, I only added the ios::binary | ios::in bit when you mentioned it in an earlier post, I must have misplaced it :). However when I move it to where it's meant to be, the result is still the same. I created a file using a hex editor that contained 80 FF, but the header file still says {0xFF,0xFF}...
You're opening as binary, but still using text methods to read the stream. Use std::istream::read().
Is this right?
1
2
3
char chr;
// ...
fFile.read(&chr, 1);    // instead of fFile.get(chr); 


If so, I've tried that and it also doesn't work.
closed account (z05DSL3A)
The following will read 100 bytes of data into a buffer:
1
2
3
4
5
6
7
8
9
10
11
12
...
    char buffer[100];
    ifstream myFile ("data.bin", ios::in | ios::binary);
    myFile.read (buffer, 100);
    if (!myFile) 
    {
        // An error occurred!
        // myFile.gcount() returns the number of bytes read.
        // calling myFile.clear() will reset the stream state
        // so it is usable again.
    }
...
Found the problem!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// readtest.cpp
#include <iostream>
#include <cstdio>
#include <fstream>
using namespace std;
int main()
{
    char buffer[2];
    ifstream myFile ("test.bin", ios::in | ios::binary);    // in ./test.bin there are two bytes, 80 followed by FF
    myFile.read(buffer, 2);
    cout << (buffer[0] == buffer[1] ? "Raw input is the same." : "Raw input is different.") << endl;
    char hexbuf[5];
    snprintf(hexbuf, 5, "0x%02X", buffer[0]);
    cout << hexbuf << ", ";
    snprintf(hexbuf, 5, "0x%02X", buffer[1]);
    cout << hexbuf << endl;
    return 0;
}


$ hex ./test.bin
0x00000000: 80 ff - ..
$ g++ ./readtest.cpp -o ./readtest
$ ./readtest
Raw input is different.
0xFF, 0xFF
$


It turns out that my choice of snprintf was wrong then... any alternatives (that work)?
Last edited on
OK, so I've got this:
1
2
3
4
5
6
7
// ... (see above posts)
string CharHex(char chr)
{
	ostringstream ss;
	ss << "0x" << hex << setw(4) << setfill('0') << (int)chr;
	return ss.str();
}

but it's giving me 0xffffff80 not 0x80 -- the setw manipulator only sets the minimum width. Any help would be appreciated.
Last edited on
I know C in a C++ world is often frowned upon, but I never liked C++ iostreams -- such a pain to use. Especially for file io. Here's a quickie thing I made which uses cstdio (read: uncompiled, untested)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <cstdio>

void PrintRow(FILE* dst,const unsigned char* buf,unsigned count)
{
  fprintf(dst,"\n    ");
  while(count > 1)
  {
    fprintf(dst,"0x%02X,",*buf);
    ++buf;
    --count;
  }
  if(count > 0)
    fprintf(dst,"0x%02X",*buf);
}

void BinToCpp(
    FILE* src, // source file, opened with "rb"
    FILE* dst) // dest file, opened with "wt"   note neither file is closed by this
{
  // determine source file size
  unsigned srcsize;
  fseek(src,0,SEEK_END);
  srcsize = (unsigned)ftell(src);
  fseek(src,0,SEEK_SET);

  // dump source file size to output file
  fprintf(dst,"\n\nconst unsigned FILE_SIZE = %u;\n\n",srcsize);

  //
  fprintf(dst,"const unsigned char FILE_DATA[] = {");

  // take the source file in 16 byte blocks
  unsigned char buf[16];
  while(srcsize > 16)
  {
    fread(buf,1,16,src);
    PrintRow(dst,buf,16);
    fprintf(dst,",");
    srcsize -= 16;
  }

  // do the rest of the file
  fread(buf,1,srcsize,src);
  PrintRow(dst,buf,srcsize);
  fprintf(dst,"\n};\n\n");
}
0x80 (or 10000000b) in char (not unsigned char) is -128. To convert a signed integer to a higher signed type, the biggest bit is copied to the new bits, so the same number in signed 32-bit integers is 0xFFFFFF80 (or 11111111 11111111 11111111 10000000b)
0x80 in unsigned char is 128. When converting an unsigned type to a bigger type, the new bits are set to zero, so 128 in 32-bit unsigned integers is 0x00000080 (or 00000000 00000000 00000000 10000000b).

Like I said in my second post, use unsigned types when dealing with binary data.
Thank you very much!
Topic archived. No new replies allowed.