how do I initialize a Guchar Gtk3

340 views Asked by At

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;
}
2

There are 2 answers

0
Scheff's Cat On BEST ANSWER

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 a char* (or a const 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:

  1. It helps the programmer to distinguish C strings from pointers to arbitrary binary data.
  2. 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 a char value is an issue, signed char or unsigned char have to be used instead. For processing bytes of binary data, it's often more convenient to handle them explicitly unsigned.

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:

  • using a global variable with a char array e.g. static char data[1024];
  • using a local variable with a char array (in a function) e.g. char data[1024];
  • allocating memory on the heap e.g. 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 and delete 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 with strcmp() or something similar, the C++ std::string provides an overloaded operator==() which allows intuitive code like e.g. std::string text = input(); if (text == "exit") exit();. Another important advantage of std::string is the internal memory management. Strings can be added to strings, inserted into strings, etc. and the std::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 makes std::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 the std::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 the std::string. Until C++11, only the std::string::c_str() had to grant a terminating 0 but not std::string::data(). With C++11, this was changed. Now, there mustn't be any difference in the return values of std::string::data() and std::string::c_str() – both functions just return the pointer to the internally stored raw data. Hence, the std::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:

std::string data;
// image file somehow read in
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*

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 that strcpy() 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 into gdk_pixbuf_loader_write(), the pointer to the raw data and the size. Thus, I suggested to drop the new[] and delete stuff completely and to replace it with the following:

std::string data;
// image file somehow read in
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, (const guchar*)data.data(), data.size(), nullptr);

For the last line, I could've used as well:

gdk_pixbuf_loader_write(loader, (const guchar*)data.c_str(), data.size(), nullptr);

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 the std::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 of char. Consequently, std::string::data() returns const char* (or char*). The gdk_pixbuf_loader_write() requires a const guchar* as second argument.

guchar is simply a

typedef unsigned char   guchar;

Hence, the const char* is converted to const 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:

string data = getFileInMem("0.0.0.0:8000/test.txt");
guchar* pixdata = (const guchar*)data.data();

GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,pixdata,sizeof(pixdata),nullptr);

Unfortunately, this solution introduces a new bug: sizeof(pixdata). While data.size() returns the size of the length of the string in data, the sizeof(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:

std::cout << sizeof (char) << std::endl;
char c;
std::cout << sizeof c << std::endl;

will output:

1
1

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:

char *c = nullptr;
std::cout << sizeof *c << std::endl;

will output:

1

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 the sizeof 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 the data but just the size of the pointer guchar*. 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:

string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();

GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, pixdata, data.size(), nullptr);

or

string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();
gsize pixdatasize = (gsize)data.size();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, pixdata, pixdatasize, nullptr);

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.

3
Ican'tThinkOfAName On

simply casting the data as a (const guchar*) should work

int main(int argc, char *argv[]){

string data = getFileInMem("0.0.0.0:8000/test.txt");


GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,(const guchar*)data.data(),data.size(),nullptr);
GdkPixbuf* imagedata = gdk_pixbuf_loader_get_pixbuf(loader);
GtkWidget *image = gtk_image_new_from_pixbuf(imagedata);

//  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;