I'm trying to display an image in a Gtk window I have the image stored in memory as a std::string and I'm trying to display it but I cant seem to get the image into a GdkPixbuf*
.
This is my Function that gets the image data i know it works because if I write the data to a file I can open it
string getFileInMem(string url){
cURLpp::Easy handle;
std::ostream test(nullptr);
std::stringbuf str;
test.rdbuf(&str);
char* error[CURL_ERROR_SIZE];
handle.setOpt(cURLpp::Options::Url(url));
handle.setOpt(cURLpp::options::FollowLocation(true));
handle.setOpt(cURLpp::options::WriteStream(&test));
handle.setOpt(cURLpp::options::ErrorBuffer(*error));
//cout << error << endl;
handle.perform();
string tmp = str.str();
return tmp;
}
this is the main loop where get FileInMem()
gets called. I've gotten the data into the guchar* and printed it but once i do that I can't write any other instructions or i get a core dumped error Any way to display the image in the window with without writing to the disk would be great
int main(int argc, char *argv[]){
string data1 = getFileInMem("0.0.0.0:8000/test.txt");
ofstream f("test.jpg");// image file
f << data;// writing image data the file
f.close();// closing the file
GdkPixbufLoader* loader = gdk_pixbuf_loader_new(); // creating a pixbuf loader
guchar* pixdata = new guchar[data.size()+1];// creating a guchar* with space for image data
strcpy((char*)pixdata,data.c_str());// copying data from string to the guchar*
gdk_pixbuf_loader_write(loader,pixdata,sizeof(pixdata),nullptr);// trying to write the data to the loader
GdkPixbuf* imagedata = gdk_pixbuf_loader_get_pixbuf(loader);// creating the pixbuf*
GtkWidget *image = gtk_image_new_from_pixbuf(imagedata);
delete pixdata;// deleting pixdata once done with it
// creating the window with the image
GtkWidget *window;
GtkWidget *button;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_add(GTK_CONTAINER(window),image);
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
I must admit that I'm not an expert concerning GDK but I've used C for years, and I'm using C++ for years, and btw. I've programmed with gtkmm (a C++ binding for GTK+) for years a while ago. So, I feel able to sort out what might appear confusing for the OP.
C Strings
The handling of strings was subject of some different approaches in the past.
E.g. in PASCAL, a string was always an array of 256 bytes. The first byte was reserved for the length of the string, the resp. number of other bytes contained the characters. This is a safe solution but it comes with disadvantages: Even the shortest string consumes 256 bytes always. Even worse, a string with more than 255 characters wouldn't be possible. There are work-arounds possible to make a list of strings in this case but that's actually annoying.
In C, it was solved differently. Strings can have arbitrary length (as long as they fit into the memory of the computer). The length itself isn't stored. Instead, the end of string is remarked by a special character
'\0'
– a byte with value 0 – which is exclusively reserved for this purpose. The disadvantage is: The length of a string has to be stored separately, or it has to be determined counting the characters up to the first occurrence of'\0'
. The C standard library provides a ready function for this: strlen(). This makes it possible to handle strings by the address of it's first character. Hence, a C string is handled by achar*
(or aconst char*
if the C string may not be modified).The C library provides some additional functions to support the work with C strings like e.g. strcpy().
strcpy()
copies consecutive bytes from a source pointer (2nd arg.) to a destination pointer (1st arg.) until the'\0'
byte occurs. (It's copied as well but the function ends afterwards.)Binary Data
Binary data (consisting of bytes with arbitrary values) can be handled similar like C strings.
char
is an integral type with the size of 1 byte. Hence, it's the suitable candidate as well. However, the binary data may contain any of the possible values 0 … 255 at any place. So, the principle to remark the end with'\0'
doesn't work. Instead, the length has to be stored separately always.For handling of binary data,
unsigned char
is often preferred. IMHO, there are two essential reasons for this:char
may be (by C standard as well as by C++ standard) signed or unsigned (depending on the decision of the resp. compiler vendor). If the signedness of achar
value is an issue,signed char
orunsigned char
have to be used instead. For processing bytes of binary data, it's often more convenient to handle them explicitlyunsigned
.The standard C library provides resp. functions for the handling of binary data as well, like e.g. memcpy(). Please, note that
memcpy()
provides a 3rd parameter to define the size of bytes to copy from source to destination pointer.Storage
Beside of the advantages of C strings, they come with a burden: The programmer is responsible providing always sufficient storage. In C, there are multiple possibilities:
char
array e.g.static char data[1024];
char
array (in a function) e.g.char data[1024];
char *data = malloc(1024);
.The size of character arrays has to be defined in the program (at compile-time). There is no way to change this at run-time of the program. (An exception are Variable Length Arrays. According to the C99 standard, they are an optional feature, but there is no such thing even in the recent C++ standards, although some C++ compilers provide them as proprietary extension.) If the size of storage is not known before runtime, allocation of dynamic memory is the only solution (i.e.
size_t n = somehowDetermined(); char *data = malloc(n);
).Managing sufficient storage sounds actually not that complicated but to organize this properly and always correctly showed as one of the essential problems in C and C++ programs over the years. (C++ inherited this issue from C. There were a
new
operator anddelete
operator added to allow type-safe allocation on heap but actually this didn't help much.) Hence, the C++ standard committee has invested a lot into safer replacements over the years.std::string
In C++, a string might be stored as
std::string
. It makes the life with strings much easier. E.g. while a C string must be compared withstrcmp()
or something similar, the C++std::string
provides an overloadedoperator==()
which allows intuitive code like e.g.std::string text = input(); if (text == "exit") exit();
. Another important advantage ofstd::string
is the internal memory management. Strings can be added to strings, inserted into strings, etc. and thestd::string
will care itself about proper allocation of storage internally.Additionally, a
std::string
stores the size of its contents internally. (The authors probably found it worth to spent the additional bytes for another integral so that it's not necessary to count the number of characters for any retrieval of string length.) That makesstd::string
a sufficient container for binary data as well.For compatibility with C APIs, the
std::string
provides a "back-door" std::string::c_str(). It provides the raw contents of thestd::string
as C string. It grants that the returned C string has a'\0'
byte after the last character i.e.std::string::c_str()[std::string::size()]
must return'\0'
. There is also a std::string::data() function to access the raw data of thestd::string
. Until C++11, only thestd::string::c_str()
had to grant a terminating 0 but notstd::string::data()
. With C++11, this was changed. Now, there mustn't be any difference in the return values ofstd::string::data()
andstd::string::c_str()
– both functions just return the pointer to the internally stored raw data. Hence, thestd::string
effectively has to place a'\0'
character at the end always regardless of the contents. This might appear as a waste but, actually, we're talking about a single additional byte, and it's a small price for a big advantage for code robustness.OPs Code
Considering that OP wants to load image files (which usually consist of arbitrary bytes) from memory, the following code is wrong:
For arbitrary binary data,
strcpy()
is wrong. It copies until the first 0 byte is found. There might be 0 bytes in the image data anywhere (e.g. if it contains black pixels). Thus, there is a high probability thatstrcpy()
copies too less bytes. In this case,memcpy()
would be the better choice.Actually, neither of both is necessary.
The
std::string data;
already contains everything what has to be fed intogdk_pixbuf_loader_write()
, the pointer to the raw data and the size. Thus, I suggested to drop thenew[]
anddelete
stuff completely and to replace it with the following:For the last line, I could've used as well:
As I already explained this doesn't make a difference since C++11. I used
data.data()
just as it looked nicer (considering the fact that the contents of thestd::string
is rather binary data than a C string).A note about the cast to
const guchar*
:std::string
stores internally a dynamically allocated array ofchar
. Consequently,std::string::data()
returnsconst char*
(orchar*
). The gdk_pixbuf_loader_write() requires aconst guchar*
as second argument.guchar is simply a
Hence, the
const char*
is converted toconst unsigned char*
. Pointer type conversions are something which should be done with care. (In general, they are the last resort for something which might be broken by design and bears the danger of Undefined Behavior – the plague of every C and C++ programmer.) In this case, the conversion is safe, and it's legal according to the C++ standard. I found another answer which explains this in detail: SO: Can I turn unsigned char into char and vice versa?.OPs Attempt to Fix the Code
After I spent some hints, the OP proposed to following fix:
Unfortunately, this solution introduces a new bug:
sizeof(pixdata)
. Whiledata.size()
returns the size of the length of the string indata
, thesizeof(pixdata)
operator is the wrong choice here.sizeof is an operator, and it's always resolved at compile time – returning the size of the type on it's right side. It may be called with a type or an expression:
will output:
Thereby, the expression doesn't even need to have valid storage at runtime as the
sizeof
is always resolved at compiletime and based on the type of the resulting expression:will output:
This might be surprising as
*c
looks like access to the contents of a null pointer (which is Undefined Behavior in general). In this case, it's effectively not. As thesizeof
operator evaluates the type at compile time the generated code just contains the result of this evaluation. Thus, no*c
happens at runtime, and no Undefined Behavior appears in the code.However,
sizeof pixdata
doesn't return the size of thedata
but just the size of the pointerguchar*
. It's probably 4 if OP compiled on a 32 bit platform, and 8 for a 64 bit platform – but it's always the same value for a certain platform.So, to fix this it has to be:
or
That became a long answer. It may illustrate that even some lines of C++ code require a lot of background knowledge to write them properly. Hence, it makes sense that entry level programmers are often hinted to get a good C++ book. I wouldn't insist that it isn't possible to learn C++ another way. However, a good C++ book is IMHO worth to be considered. There are a lot of pitfalls in C++, most of them inherited from C and some of them specifically introduced in C++ itself.