1
votes

I've faced with The white square problem at SFML C++ while I'm trying to display a textured button. I've got an ImageButton.h which inherits from Button.h. The texture is loaded successfully (checked in debugger). But in the end, I see a white square. How to solve it?

Button.h

#ifndef BUTTON_H
#define BUTTON_H

#include<SFML/Graphics.hpp>

class Button
{
public:
    Button();
    Button(sf::Vector2f size, sf::Vector2f pos,sf::Color outlineColor, float sizeOutline);
    void virtual draw(sf::RenderWindow* w) = 0;
protected:
    sf::RectangleShape frame;
};



#endif // !BUTTON_H

Button.cpp

#include "Button.h"

Button::Button()
{
}

Button::Button(sf::Vector2f size, sf::Vector2f pos,sf::Color outlineColor, float sizeOutline)
{
    frame.setPosition(pos);
    frame.setSize(size);
    frame.setOutlineColor(outlineColor);
    frame.setOutlineThickness(sizeOutline);
}


ImageButton.h

#ifndef IMAGE_BUTTON_H
#define IMAGE_BUTTON_H

#include"Button.h"

class ImageButton : public Button
{
public:
    ImageButton();
    ImageButton(sf::Vector2f size, sf::Vector2f pos, sf::Color outlineColor, float sizeOutline, std::string path);
    void draw(sf::RenderWindow* w);
private:
    sf::Texture backTexture;
    sf::Sprite background;
};


#endif // !IMAGE_BUTTON_H

ImageButton.cpp

#include "ImageButton.h"

ImageButton::ImageButton()
{
}

ImageButton::ImageButton(sf::Vector2f size, sf::Vector2f pos, sf::Color outlineColor, float sizeOutline, std::string path)
    : Button(size,pos,outlineColor,sizeOutline)
{
    
    backTexture.loadFromFile(path, sf::IntRect(sf::Vector2i(pos.x,pos.y),sf::Vector2i(size.x,size.y)));
    backTexture.setSmooth(true);
    background.setTexture(backTexture);
    background.setPosition(pos);
}

void ImageButton::draw(sf::RenderWindow* w)
{
    w->draw(this->background);
    w->draw(this->frame);
}

programm.h

#ifndef PROGRAMM_H
#define PROGRAMM_H

#include<SFML/Graphics.hpp>
#include"ImageButton.h"


class programm
{
public:
    programm();
    void run();
private:
    ImageButton b;
    sf::RenderWindow* window;
    sf::Event e;
    void render();
    void update();

};
#endif // !PROGRAMM_H

programm.cpp

#include "programm.h"

programm::programm()
{
    this->window = new sf::RenderWindow(sf::VideoMode(600, 600), "Novel Editor", sf::Style::Close);
    this->window->setPosition(sf::Vector2i(0, 0));
    this->window->setFramerateLimit(60);
    this->b = ImageButton(sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f), sf::Color::Yellow, 5.f, "images\\putin.png");
}

void programm::run()
{
    while (this->window->isOpen())
    {
        while (this->window->pollEvent(e))
        {
            update();
        }
    }
}

void programm::render()
{
    this->window->clear();
    b.draw(this->window);
    this->window->display();
}

void programm::update()
{
    switch (e.type)
    {
    case sf::Event::Closed:
    {
        this->window->close();
        break;
    }
    default:
        break;
    }

    render();
}

Screenshot

enter image description here

2
As I learned, SFML is building on smart pointers, so the assignment of ImageButton to this->p could work, but depends on the assignment operators of sf::Texture or sf::Sprite being correctly implemented (are they?). So I'd try to move the list of ImageButton constructor arguments to the initializer list : b(<here>). BTW: the new in this->window = new sf::RenderWindow(... and sf::RenderWindow* window; in program.h seems quite inconsistent to me, it's dangerous to mix automatic and dynamic variables this way. - Wolf
If you look at this reddit.com/r/sfml/comments/68hmcb/white_square_problem you'll see the same pattern, an automatic Player object is assigned to an existing one (player). It really looks as if there is something wrong in (at least one of) the assignment operators in sf::Texture and sf::Sprite - Wolf

2 Answers

2
votes
programm::programm()
{
    ...
    this->b = ImageButton(...); //< this line cause the bug
}

You are initializing your image button this->b by assigning from a local ImageButton. Now the sprite will have the texture reference of the local instance's texture, and when the local instance will "die" the texture will be freed. You need to maintain the lifetime of the texture

Solution 1: override the assignment operator and set texture there like this

ImageButton& operator=(const ImageButton& ref) {
        backTexture = ref.backTexture;
        background = ref.background;
        background.setTexture(backTexture);
        return *this;
}

Solution 2: Create a TextureManager and make it as your texture API for all your program, maintain your texture lifetime through it.

Solution 3: initialize the image button at programm constructor

programm::programm()
    :b (sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f), sf::Color::Yellow, 5.f, "img.jpg")
{
   ...
}

Another bug

void ImageButton::draw(sf::RenderWindow* w)
{
    w->draw(this->background);
    w->draw(this->frame);
}

you're drawing your frame after the background, and your frame's fill color by default is white

Solution 1: draw frame first and draw background next

Solution 2: set your frame fill color alpha to 0

Button::Button(sf::Vector2f size, sf::Vector2f pos, sf::Color outlineColor, float sizeOutline)
{
    frame.setPosition(pos);
    frame.setSize(size);
    frame.setOutlineColor(outlineColor);
    frame.setFillColor(sf::Color(0, 0, 0, 0)); // <----
    frame.setOutlineThickness(sizeOutline);
}
2
votes

The problem you and others (on reddit, on SO, and elsewhere) are facing is caused by the way the constructor and assignment operator of sf::Sprite are (not) implemented: Since the developers decided to not implement specialized functions and didn't make them private, the compiler provides defaults that are not aware of the semantics of the m_texture pointer and therefore doing a binary copy. This may be not the best approach but it's documented.

So if you are using a sf::Sprite member in your class without taking special measures for copying, the compiler will assume default assignment semantics and therefore invoke the (invalid) assignment semantics of the sf::Sprite class. The local ImageButton object you create in the program invokes this default assignment of ImageButton

programm::programm()
{
    this->window = new sf::RenderWindow(sf::VideoMode(600, 600), "Novel Editor", sf::Style::Close);
    this->window->setPosition(sf::Vector2i(0, 0));
    this->window->setFramerateLimit(60);
    this->b = ImageButton(sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f), sf::Color::Yellow, 5.f, "images\\putin.png");
}

the Sprite in member b takes over the pointer texture from the Sprite within the local ImageButton whose lifetime is limited to the scope of the programm constructor. Beyond that, the sprite in your member b holds a reference to a destroyed texture object which causes is undefined behaviour (see also sf::Sprite Class Reference (SFML / Learn / 2.4.1 Documentation)):

If the source texture is destroyed and the sprite tries to use it, the behavior is undefined.

By the way, I'd suggest directly using sf::RenderWindow window in the program class not via pointer, so you could change your code to something like this (note that I used button instead of b and shortened inlined the implementation for the sake of brevity).

class programm
{
public:
    programm():
        window(sf::VideoMode(600, 600), "Novel Editor", sf::Style::Close);
        button(sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f), 
        sf::Color::Yellow, 5.f, "images\\putin.png")
    {
        window->setPosition(sf::Vector2i(0, 0));
        window->setFramerateLimit(60);
    }
    // ...
private:
    // ...
    sf::RenderWindow window;
    ImageButton button;
};

Another option would be to provide a specialized assignment operator (thakee nathees shows how) or copy constructor for your ImageButton class that takes care of the deep copy of the texture object.

Alternatively, you could think about implementing a wrapper class for sf::Sprite to fix this issue and use this instead for you application(s).