According to the documentation, one can load an sf::Texture three different ways: from file, from stream, from memory. I think I need the latter, but I'm not even sure about that. And if I do, I can't figure out how to use it.
I have a method, which, given an url to work with, needs to return an sf::Sprite. I'm successfully downloading the binary data, and storing it in a string (since Http::Response::getBody() returns that). I know for a fact, that this is the right data, because if I write and save it to a file, I can view the image. I don't want a file however, I just need the image displayed.
Here is the function:
sf::Sprite Downloader::GetPicture(const std::string &url)
{
sf::Http http;
std::string host = url.substr(0, url.find("net/")+4 );
std::string uri = url.substr(url.find("net/")+4, url.size());
http.setHost(host);
request.setUri(uri);
response = http.sendRequest(request);
std::string data = response.getBody();
//THIS IS WRONG
sf::Texture texture;
texture.loadFromMemory((void*)data, sizeof(data));
return sf::Sprite(texture);
/* THIS WORKS, BUT I DON'T WANT TO SAVE, REOPEN, THEN DELETE AT THE END
std::ofstream fileout("test.jpg", std::ios::binary);
fileout << data;
fileout.close();
*/
}
Here is loadFromMemory's signature for the lazy (the void* confuses me, that's probably the problem).
Also, this might be a completely wrong way to do it; maybe extending sf::InputStream, and using loadFromStream?
The problem is, returned sf::Sprite has 0×0 dimension and 0 size, that's how I know it's wrong.
There are two mistakes in your code. One was spotted by Casey, that is you should use texture.loadFromMemory(data.data(), data.length()).
The problem with texture.loadFromMemory((void*)data, sizeof(data)); is that you will load some garbage and not necessarily the picture data; you will load the other attributes of the string (like an integer for the length, and maybe a pointer for the data itself).
The other one is related to the white square problem: you return a sprite but not the texture. After the function returns, the texture is destroyed and the sprite will display only a white rectangle instead.
You could do something like:
sf::Texture dlpicture(std::string host, std::string uri)
{
sf::Http http(host);
sf::Http::Request request(uri);
auto response = http.sendRequest(request);
auto data = response.getBody();
sf::Texture text;
text.loadFromMemory(data.data(), data.length());
return text;
}
which you can try with sf::Texture texture = dlpicture("www.sfml-dev.org", "download/goodies/sfml-logo-small.png");.
SFML indeed support jpg format but not all its variants. Progressive jpg is not supported for example. You'll have to use another format.
Related
Seeing a weird crash due to read access violation. Here is the minimal code:
struct MyFile : QFile
{
...
string read ()
{
QByteArray content;
if(<something>)
content = QFile::readAll();
...
string buffer(QFile::size(), 0);
if(content.isEmpty())
{
QFile::seek(offset);
QFile::read(&buffer[0], buffer.size());
}
else
::memcpy(&buffer[0], content.data(), buffer.size());
// ^^^^ 40034 ^^^^ 42690
return buffer;
}
}
Here it's trying to read a .png file. Somehow the QFile::size() returns 42690, while the QFile::readAll() which is stored in content has a size of 40034.
Unfortunately the filename is not handy to verify the actual size. Writing test code for text or png files, it always gives proper results.
How is that possible?
Below is a debug frame for reference:
You code does not take the current file's seek position into account. Thus, in case there were some read operations already, it will crash, because QFile::readAll will return only a part of the file (from the current seek position till the end of file).
The other possibility is the use of QIODeviceBase::Text as mentioned in comments, but you're not using it, so it's not the case.
I'm working on making some Spotify API calls on an ESP32. I'm fairly new to C++ and while I seem to got it working how I wanted it to, I would like to know if it is the right way/best practice or if I was just lucky. The whole thing with chars and pointers is still quite confusing for me, no matter how much I read into it.
I'm calling the Spotify API, get a json response and parse that with the ArduinoJson library. The library returns all keys and values as const char*
The library I use to display it on a screen takes const char* as well. I got it working before with converting it to String, returning the String with the getTitle() function and converting it back to display it on screen. After I read that Strings are inefficient and best to avoid, I try to cut out the converting steps.
void getTitle()
{
// I cut out the HTTP request and stuff
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, http.getStream(), );
JsonObject item = doc["item"];
title = item["name"]; //This is a const char*
}
const char* title = nullptr;
void loop(void) {
getTitle();
u8g2.clearBuffer();
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(1, 10, title);
u8g2.sendBuffer();
}
Is it okay to do it like that?
This is not fine.
When seeing something like this, you should immediately become suspicious.
This is because in getTitle, you are asking a local object (item) for a pointer-- but you use the pointer later, when the item object no longer exists.
That means your pointer might be meaningless once you need it-- it might no longer reference your data, but some arbitrary other bytes instead (or even lead to crashes).
This problem is independent of what exact library you use, and you can often find relevant, more specific information by searching your library documentation for "lifetime" or "object ownership".
FIX
Make sure that item (and also DynamicJsonDocument, because the documentation tells you so!) both still exist when you use the data, e.g. like this:
void setTitle(const char *title)
{
u8g2.clearBuffer();
u8g2.setDrawColor(1);
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(1, 10, title);
u8g2.sendBuffer();
}
void updateTitle()
{
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, http.getStream(), );
JsonObject item = doc["item"];
setTitle(item["name"]);
}
See also: https://arduinojson.org/v6/how-to/reuse-a-json-document/#the-best-way-to-use-arduinojson
Edit: If you want to keep parsing/display update decoupled
You could keep the JSON document "alive" for when the parsed data is needed:
/* "static" visibility, so that other c/cpp files ("translation units") can't
* mess mess with our JSON doc directly
*/
static DynamicJsonDocument doc(1024);
static const char *title;
void parseJson()
{
[...]
// super important to avoid leaking memory!!
doc.clear();
DeserializationError error = deserializeJson(doc, http.getStream(), );
// TODO: robustness/error handling (e.g. inbound JSON is missing "item")
title = doc["item"]["name"];
}
// may be nullptr when called before valid JSON was parsed
const char* getTitle()
{
return title;
}
This is my texture manager class.
class TextureManager
{
public:
static std::map<std::string, Texture2D> Textures;
static Texture2D LoadTexture(const GLchar *file, GLboolean alpha,
std::string name);
// Retrieves a stored texture
static Texture2D GetTexture(std::string name);
// Properly de-allocates all loaded resources
static void Clear();
private:
TextureManager() { }
// Loads a single texture from file
static Texture2D loadTextureFromFile(const GLchar *file, GLboolean
alpha);
};
what i plan to do is to give the path of the image as the string parameter of the map .
when i need to load another image i will first check the Map with the given path if the image is loaded or not.
I have two questions.
1) Is this a acceptable work flow to check if the image is loaded or not
2) Can we use the path of the image as a string value of the map.
First, before I even answer the question, I'd strongly recommend NOT making the member vars and functions of this class static. I know initially it may seem sensible to maintain a single global set of textures, however in many cases this quickly becomes a little rubbish.
For example, let's say I have textures for a level in a game, and a set for the GUI. I then need to dump the old level textures, and load the new set for the next level, without touching those for the GUI. If all the textures exist within a single object, then it will require a little bit of work to figure out which ones I need to delete. If however they are in two texture managers (one for the GUI, one for the level), then all I need to do is delete the texture manager for the level, and create a new one for the next level.
Your current design (simply nuke all the textures) would cause havok for any loading screens / GUI textures that may be present when loading a new level.
There is nothing fundamentally wrong with using the file path as a key for your texture lookup, BUT there are a few edge cases you may need to address before it will become a robust class:
Convert all backslashes to forward slashes: e.g. C:\files\foo.jpg ---> convert to ---> C:/files/foo.jpg. This avoids the issue on windows that you can use / or .
On Windows ONLY, convert all the characters to lowercase. i.e. "C:/foo.txt", "c:/FoO.TxT", etc: they all refer to the same file. On linux/mac, those are different files. On windows they are the same file.
Beware of relative v.s. absolute paths. Ideally you'd ONLY ever use relative paths, and those would be your keys. This avoids the issue of loading C:/files/foo.jpg and ./foo.jpg.
Beware of "./foo.jpg" and "foo.jpg"
If you can handle those cases, then it should work reliably. I would however suggest a slight change to the API for purely performance reasons:
typedef uint32_t UniqueTextureId;
class TextureManager
{
private:
// increment and return for each new texture loaded.
// zero should indicate an invalid texture
UniqueTextureId m_idGenerator = 0;
// a lookup to turn the file path into a unique ID
std::unordered_map<std::string, UniqueTextureId> m_filePathToId;
// a lookup to get the texture from an ID
std::unordered_map<UniqueTextureId, Texture2D> m_idToTexture;
// a method to take a raw filepath and...
// 1. convert \ to /
// 2. make lower case on windows
// 3. convert any absolute paths to relative paths
// 4. If a paths starts with ./, remove the first two chars.
std::string tidyUpFilePath(std::string str);
// turn a file path into an integer
UniqueTextureId filePathToId(std::string messyPath)
{
std::string tidyPath = tidyUpFilePath(messyPath);
const auto& it = m_filePathToId.find(tidyPath);
if(it != m_filePathToId.end())
{
return it->second;
}
return 0;
}
public:
// return a unique ID for this texture (which can probably be
// unique only within the current TextureManager).
UniqueTextureId LoadTexture(const GLchar *file, GLboolean alpha, std::string name);
// Do not return a Texture2D object! (return a pointer instead)
// if you return a copy of the texture, there is a good chance
// the destructor will end up calling glDeleteTextures each
// time the returned object is destroyed, which will cause a right
// bother for the next frame!
const Texture2D* GetTexture(UniqueTextureId id)
{
const auto& it = m_idToTexture.find(id);
if(it != m_idToTexture.end())
{
return &it->second;
}
// Just to guard against the case where the id is invalid.
return nullptr;
}
};
One other word of caution. If you are in the situation where there may be multiple calls to loadTexture(), you should probably consider reference counting the textures. This would allow you to have a nice symmetric deleteTexture() method (where it first decrements the ref count, and only deletes the texture when the ref count hits zero).
I have an ID3D11Texture2D. The end goal is to take the image and embed it into html as a base64 encoded string (requirements are based on existing functionality of which I have no control). I can achieve this by saving the texture to file using DirectX::SaveWICTextureToFile, loading the texture as a byte array, then encoding the array in base64, but I'd like to do it without saving to a file if possible.
Ideally there would be some kind of SaveWICTextureToMemory function that can take the container format as a parameter, but so far I haven't found anything that deals with the format. I've checked out CreateWICTextureFromMemory and other functions in the WICTextureLoader package, but I didn't find exactly what I'm looking for (perhaps I missed it). Figuring SaveWICTextureToFile must generate a byte array to write to file, I tried to essentially unpack that function and strip away the file creation elements of it, but decided there was likely a simpler solution, which brought me here.
I don't have much code to look at, as I've only stubbed out many of the key functions, but this is generally where this stuff needs to live:
HRESULT ExportImages::GetAngleEncodedString(std::string& encoded, const DirectX::XMMATRIX& projectionMatrix, float radians) const
{
// Draws geometry to m_Texture2D
DrawAngle(projectionMatrix, radians);
unsigned const char* pngBytes = GetPNGBytes(m_Texture2D);
try
{
encoded = Base64Encode(pngBytes);
}
catch (...)
{
// TODO don't catch all, add resolution
return HRESULT(-1);
}
return S_OK;
}
unsigned const char* ExportImages::GetPNGBytes(ID3D11Texture2D* texture) const
{
// ???
}
I have a QGraphicsScene where I am drawing a QPainterPath, and I need to be able to save the shape, and redraw it when the app runs again. Here is how I'm drawing the shape, simplified version, and my write method.
void drawPath(){
QPoint p1 = QPoint(10, 20);
writePointsToFile(p1);
QPoint p2 = QPoint(25, 30);
writePointsToFile(p2);
QPoint p3 = QPoint(40, 60);
writePointsToFile(p3);
QPainterPath path;
path.moveTo(p1.x(), p1.y());
path.lineTo(p2.x(), p2.y());
path.lineTo(p3.x(), p3.y());
}
void writePointsToFile(QPoint point){
QFile file("../path.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << point;
file.close();
}
Currently, my file is never written to when it runs.
But beyond that, is the correct way to serialize this data so that I can rebuild the shape?
I thought I would be able to handle re-drawing, but I'm not understanding the serialization well enough.
Do I serialize the points?
The List containing the points?
My thought was if I serialize the points, when I deserialize, I then add them to a list, and I should be able to recreate the shape based on the position of each point in the list; ie point at position 0 would be p1, point at 1 would be p2, etc. But I can't get this far because nothing is being written to the file anyway. Plus I'm not entirely sure what to expect from the serialization of the data in the first place.
Any help on this would be wonderful.
EDIT: based on feedback, I am now trying to this in my write method
QFile file("../path.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
QDataStream & operator << (QDataStream& file, const QPainterPath& path);
out << path;
file.close();
This compiles fine, even though I'm not entirely sure I'm doing that right, nothing is being written to the file so I assume I'm still off somewhere.
Qt already provides the operators, necessary to serialize and deserialize the QPainterPath directly:
QDataStream & operator<<(QDataStream & stream, const QPainterPath & path)
QDataStream & operator>>(QDataStream & stream, QPainterPath & path)
So there is no need to serialize points, when you can serialize the exact content of the path, including complex multi-component paths.
So you should implement the path as a persistent member variable, so that you can read it from or write it to a file, and in the draw method you simply draw the path.
Currently, my file is never written to when it runs.
My bet is because writePointsToFile() is never invoked. You may also get in the good habit of checking for errors when you try and open files and such. You also don't specify QIODevice::Append so even if you did write to disk, you'd only write one single point, overwriting the previous each time.
Edit: based on your edit, it looks like you are getting ahead of yourself, and still have to learn basic C++ before you rush into using it. Try something like this, and figure where you are going wrong:
QPoint p1 = QPoint(10, 20);
QPoint p2 = QPoint(25, 30);
QPoint p3 = QPoint(40, 60);
QPainterPath path;
path.moveTo(p1.x(), p1.y());
path.lineTo(p2.x(), p2.y());
path.lineTo(p3.x(), p3.y());
QFile file("../path.dat");
if (!file.open(QIODevice::WriteOnly)) return;
QDataStream out(&file);
out << path;