Vocabulary
Most (if not all) of you are already familiar with these two very common objects, so let’s define them very briefly.
A texture is an image. But we call it «texture» because it has a very specific role: being mapped to a 2D entity.
A sprite is nothing more than a textured rectangle.
Ok, that was short but if you really don’t understand what sprites and textures are, then you’ll find a much better description on Wikipedia.
Loading a texture
Before creating any sprite, we need a valid texture. The class that encapsulates textures in SFML is, surprisingly, sf::Texture
.
Since the only role of a texture is to be loaded and mapped to graphical entities, almost all its functions are about loading and updating it.
The most common way of loading a texture is from an image file on disk, which is done with the loadFromFile
function.
sf::Texture texture;
if (!texture.loadFromFile("image.png"))
{
// error...
}
The loadFromFile
function can sometimes fail with no obvious reason. First, check the error message that SFML prints to the standard
output (check the console). If the message is unable to open file
, make sure that the working directory (which is the directory
that any file path will be interpreted relative to) is what you think it is:
When you run the application from your desktop environment, the working directory is the executable folder. However, when you launch your program from your IDE
(Visual Studio, Code::Blocks, …) the working directory might sometimes be set to the project directory instead. This can usually be changed
quite easily in the project settings.
You can also load an image file from memory (loadFromMemory
), from a
custom input stream (loadFromStream
), or from an
image that has already been loaded (loadFromImage
). The latter loads the texture from an sf::Image
, which is a utility class that
helps store and manipulate image data (modify pixels, create transparency channel, etc.). The pixels of an sf::Image
stay in system memory,
which ensures that operations on them will be as fast as possible, in contrast to the pixels of a texture which reside in video memory and are
therefore slow to retrieve or update but very fast to draw.
SFML supports most common image file formats. The full list is available in the API documentation.
All these loading functions have an optional argument, which can be used if you want to load a smaller part of the image.
// load a 32x32 rectangle that starts at (10, 10)
if (!texture.loadFromFile("image.png", sf::IntRect(10, 10, 32, 32)))
{
// error...
}
The sf::IntRect
class is a simple utility type that represents a rectangle. Its constructor takes the coordinates of the
top-left corner, and the size of the rectangle.
If you don’t want to load a texture from an image, but instead want to update it directly from an array of pixels, you can create it empty and
update it later:
// create an empty 200x200 texture
if (!texture.create(200, 200))
{
// error...
}
Note that the contents of the texture are undefined at this point.
To update the pixels of an existing texture, you have to use the update
function. It has overloads for many kinds of data sources:
// update a texture from an array of pixels
sf::Uint8* pixels = new sf::Uint8[width * height * 4]; // * 4 because pixels have 4 components (RGBA)
...
texture.update(pixels);
// update a texture from a sf::Image
sf::Image image;
...
texture.update(image);
// update the texture from the current contents of the window
sf::RenderWindow window;
...
texture.update(window);
These examples all assume that the source is of the same size as the texture. If this is not the case, i.e. if you want to update only a part of the
texture, you can specify the coordinates of the sub-rectangle that you want to update. You can refer to the documentation for further details.
Additionally, a texture has two properties that change how it is rendered.
The first property allows one to smooth the texture. Smoothing a texture makes pixel boundaries less visible (but the image a little more blurry), which can be
desirable if it is up-scaled.
texture.setSmooth(true);
Since smoothing samples from adjacent pixels in the texture as well, it can lead to the unwanted side effect of factoring in pixels outside the selected texture area.
This can happen when your sprite is located at non-integer coordinates.
The second property allows a texture to be repeatedly tiled within a single sprite.
texture.setRepeated(true);
This only works if your sprite is configured to show a rectangle which is larger than the texture, otherwise this property has no effect.
Ok, can I have my sprite now?
Yes, you can now create your sprite.
sf::Sprite sprite;
sprite.setTexture(texture);
… and finally draw it.
// inside the main loop, between window.clear() and window.display()
window.draw(sprite);
If you don’t want your sprite to use the entire texture, you can set its texture rectangle.
sprite.setTextureRect(sf::IntRect(10, 10, 32, 32));
You can also change the color of a sprite. The color that you set is modulated (multiplied) with the texture of the sprite. This can also
be used to change the global transparency (alpha) of the sprite.
sprite.setColor(sf::Color(0, 255, 0)); // green
sprite.setColor(sf::Color(255, 255, 255, 128)); // half transparent
These sprites all use the same texture, but have a different color:
Sprites can also be transformed: They have a position, an orientation and a scale.
// position
sprite.setPosition(sf::Vector2f(10.f, 50.f)); // absolute position
sprite.move(sf::Vector2f(5.f, 10.f)); // offset relative to the current position
// rotation
sprite.setRotation(90.f); // absolute angle
sprite.rotate(15.f); // offset relative to the current angle
// scale
sprite.setScale(sf::Vector2f(0.5f, 2.f)); // absolute scale factor
sprite.scale(sf::Vector2f(1.5f, 3.f)); // factor relative to the current scale
By default, the origin for these three transformations is the top-left corner of the sprite. If you want to set the origin to a different point
(for example the center of the sprite, or another corner), you can use the setOrigin
function.
sprite.setOrigin(sf::Vector2f(25.f, 25.f));
Since transformation functions are common to all SFML entities, they are explained in a separate tutorial:
Transforming entities.
The white square problem
You successfully loaded a texture, constructed a sprite correctly, and… all you see on your screen now is a white square. What happened?
This is a common mistake. When you set the texture of a sprite, all it does internally is store a pointer to the texture instance.
Therefore, if the texture is destroyed or moves elsewhere in memory, the sprite ends up with an invalid texture pointer.
This problem occurs when you write this kind of function:
sf::Sprite loadSprite(std::string filename)
{
sf::Texture texture;
texture.loadFromFile(filename);
return sf::Sprite(texture);
} // error: the texture is destroyed here
You must correctly manage the lifetime of your textures and make sure that they live as long as they are used by any sprite.
The importance of using as few textures as possible
Using as few textures as possible is a good strategy, and the reason is simple: Changing the current texture is an expensive operation for the
graphics card. Drawing many sprites that use the same texture will yield the best performance.
Additionally, using a single texture allows you to group static geometry into a single entity (you can only use one texture per draw
call), which will
be much faster to draw than a set of many entities. Batching static geometry involves other classes and is therefore beyond the scope of this tutorial, for further details
see the vertex array tutorial.
Try to keep this in mind when you create your animation sheets or your tilesets: Use as little textures as possible.
Using sf::Texture with OpenGL code
If you’re using OpenGL rather than the graphics entities of SFML, you can still use sf::Texture
as a wrapper around an OpenGL texture object and use it
along with the rest of your OpenGL code.
To bind a sf::Texture
for drawing (basically glBindTexture
), you call the bind
static function:
sf::Texture texture;
...
// bind the texture
sf::Texture::bind(&texture);
// draw your textured OpenGL entity here...
// bind no texture
sf::Texture::bind(NULL);
Это может быть простой вопрос, но мне трудно найти прямой ответ на него: есть ли способ изменить размер загруженной текстуры в SFML 2.0? Например, если у меня есть 3264×2448 png, и я хочу уменьшить его до размера окна 900×1200 без обрезки, как бы я это сделал?
3
Решение
Есть ли способ масштабировать все визуализированные окна под любой монитор?
какой системы работает приложение?
Во-первых, вот способ масштабирования изображения до текущего размера RenderWindow.
// assuming the given dimension
// change this dynamically with the myRenderWindow->getView().getSize()
sf::Vector2f targetSize(900.0f, 1200.0f);
yourSprite.setScale(
targetSize.x / yourSprite.getLocalBounds().width,
targetSize.y / yourSprite.getLocalBounds().height);
Имейте в виду, что это может растянуть ваше изображение, если соотношение сторон не поддерживается. Возможно, вы захотите добавить код, чтобы скорректировать решение для вашего случая.
Затем, если вы хотите растянуть окно RenderWindow на весь экран, могу ли я предложить вам использовать полноэкранный режим?
Вот фрагмент того, как это делается:
// add the flag to the other ones
mStyleFlag = sf::Style::Default | sf::Style::Fullscreen;
// get the video mode (which tells you the size and BPP information of the current display
std::vector<sf::VideoMode> VModes = sf::VideoMode::getFullscreenModes();
// then create (or automatically recreate) the RenderWindow
mMainWindow.create(VModes.at(0), "My window title", mStyleFlag);
5
Другие решения
void RenderWindow::BackgroundSize()
{
sf::Vector2u size = backgroundTexture.getSize();
float xsize = (window.getSize().x / size.x);
float ysize = (window.getSize().y / size.y);
float sScale;
if (xsize > ysize)
{
sScale = ysize;
}
else
{
sScale = xsize;
}
backgroundSprite.setScale(sScale, sScale);
sf::FloatRect fRect = backgroundSprite.getGlobalBounds();
float px = (window.getSize().x - fRect.width) / 2;
float py = (window.getSize().y - fRect.height) / 2;
backgroundSprite.setPosition(px, py);
}
0
Just started learning SFML (so please bear with me).
I have made a RenderWindow object, and I have a image I want to fit perfectly to that window.
By searching through the documentation, I found the sf::Sprite::SetScale function. Is this the right function to do it? But how do I set the scale of the sprite to the scale of the RenderWindow, when the RenderWindow object size is set in pixels? Do I have to get the scale of the RenderWindow, and then assign the background sprite to that scale or?
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <iostream>
int main()
{
sf::RenderWindow window(sf::VideoMode(600, 300), "Simple Game");
sf::Texture BackgroundTexture;
sf::Sprite background;
//background.setScale(0.2, 0.2); <--- how?
if(!BackgroundTexture.loadFromFile("media/background.png"))
{
return -1;
}
else
{
background.setTexture(BackgroundTexture);
}
while(window.isOpen())
{
sf::Event event;
while(window.pollEvent(event))
{
switch(event.type)
{
case sf::Event::Closed:
window.close();
}
}
window.clear();
window.draw(background);
window.display();
}
}
asked Apr 6, 2016 at 10:15
The scale factor you need is simply the ratio between the window size and the texture size. sf::Texture has a getSize function, as has sf::RenderWindow. Simply get the sizes of both, calculate the ratio and use it to set the scale of the sprite, like this:
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <iostream>
int main()
{
sf::RenderWindow window(sf::VideoMode(600, 300), "Simple Game");
sf::Texture BackgroundTexture;
sf::Sprite background;
sf::Vector2u TextureSize; //Added to store texture size.
sf::Vector2u WindowSize; //Added to store window size.
if(!BackgroundTexture.loadFromFile("background.png"))
{
return -1;
}
else
{
TextureSize = BackgroundTexture.getSize(); //Get size of texture.
WindowSize = window.getSize(); //Get size of window.
float ScaleX = (float) WindowSize.x / TextureSize.x;
float ScaleY = (float) WindowSize.y / TextureSize.y; //Calculate scale.
background.setTexture(BackgroundTexture);
background.setScale(ScaleX, ScaleY); //Set scale.
}
while(window.isOpen())
{
sf::Event event;
while(window.pollEvent(event))
{
switch(event.type)
{
case sf::Event::Closed:
window.close();
}
}
window.clear();
window.draw(background);
window.display();
}
}
answered Apr 6, 2016 at 12:18
UnimportantUnimportant
2,06614 silver badges19 bronze badges
0
25.04.2014
Термины
Вероятно вы уже знакомы с этими терминами, но всё же давайте кратко их определим.
Текстура — это изображение. Мы же называем изображение «текстурой» из-за его специфической роли: их наносят на 2D объекты.
Спрайт — это текстурированный прямоугольник.
Загрузка текстуры
Перед созданием любого спрайта, нам нужна готовая текстура. В SFML класс для работы с текстурами называется sf::Texture. Поскольку единственная роль текстуры после её загрузки заключается в нанесении её на графический объект, то почти все методы этого класса связаны с загрузкой и обновлением.
Наиболее распространённый способ загрузки текстуры — это загрузка из файла изображения лежащего на диске. Осуществляется это функцией loadFromFile():
sf::Texture texture; if (!texture.loadFromFile("image.png")) { // ошибка... }
Иногда функция loadFromFile() терпит неудачу по не очевидной причин. Сначала проверьте сообщение об ошибке выводимое в стандартный вывод (проверьте консоль). Если сообщение гласит unable to open file (не удалось открыть файл), убедитесь что рабочий каталог (это каталог относительно которого и будут интерпретироваться все пути к файлам) именно там где вы предполагаете: когда вы запускаете приложение из проводника, рабочим каталогом является папка с приложением, но когда вы запускаете вашу программу через IDE (Visual Studio, Code :: Blocks, …), то рабочий каталог иногда устанавливается в папку с проектом, как правило это легко изменить в настройках проекта.
Аналогично вы можете загрузить некоторый файл изображение из памяти (loadFromMemory()), из пользовательского входного потока (loadFromStream()) или из уже загруженного изображения (loadFromImage()). Последняя из этих функций загружает текстуру из объекта класса sf::Image. Этот класс является служебным и помогает управлять изображениями (изменять пиксели, создавать альфа-канал, и т.д.) Пиксели изображения класса sf::Image находятся в системной памяти, это гарантирует, что операции над ними будут производится быстрее, чем над пикселями текстуры, которые находятся в видеопамяти и которые долго получать и обновлять, но очень быстро можно нарисовать.
SFML поддерживает большинство из распространённых форматов файлов изображений. Полный перечень указан в API документации.
Все перечисленные функции загрузки могут принимать один необязательный аргумент, который позволяет загрузить лишь часть некоторого изображения.
// загружаем прямоугольник 32x32 начало которого в точке (10, 10) if (!texture.loadFromFile("image.png", sf::IntRect(10, 10, 32, 32))) { // ошибка... }
Класс sf::IntRect это простой и удобный тип, олицетворяющий прямоугольник. Конструктор класса принимает левый верхний угол и размеры прямоугольника.
Если вы не хотите загружать текстуру из изображения, а хотите обновить их немедленно из массива пикселей, вы можете создать пустую текстуру и обновить её потом.
// создаём пустую текстуру 200x200 if (!texture.create(200, 200)) { // ошибка... }
Заметьте, в этот момент содержимое текстуры не определено.
Для обновления пикселей существующей текстуры, вы должны использовать функцию update(). Она может принимать различные ресурсы в качестве аргументов.
// обновление текстуры из массива пикселей sf::Uint8* pixels = new sf::Uint8[width * height * 4]; // * 4 т.к. пиксели имеют четыре компоненты (RGBA) ... texture.update(pixels); // обновление текстуры из sf::Image sf::Image image; ... texture.update(image); // обновление текстуры из текущего содержимого окна sf::RenderWindow window; ... texture.update(window);
В этих примерах предполагалось что источники имеют те же размеры что и текстура. Если это не так, т.е. если вы хотите обновить лишь часть текстуры, то вы можете указать координаты доп. прямоугольника которые вы хотите обновить.
Кроме того, текстура имеет два свойства, которые влияют на визуализацию.
Первое из них — это сглаживание текстуры. Сглаживание текстуры делает её пиксели менее видимыми (более размытыми), что может быть важно при масштабировании.
texture.setSmooth(true);
Поскольку сглаживание интерполирует соседние пиксели в текстуре, оно может дать нежелательный побочный эффект в виде отображения пикселей находящихся вне области текстуры. Это может произойти когда ваш спрайт находится на нецелых координатах.
Второе свойство позволяет повторять текстуру в пределах одного спрайта.
texture.setRepeated(true);
Это будет работать только если прямоугольник спрайта больше чем текстура, в противном случае эффекта не будет.
Хорошо, теперь то я уже могу нарисовать свой спрайт?
Да, теперь можно приступить к рисованию спрайтов.
Создаём спрайт:
sf::Sprite sprite; sprite.setTexture(texture);
Рисуем его:
// внутри главного цикла, между window.clear() и window.display() window.draw(sprite);
Если вы не хотите что бы ваш спрайт отображал всю текстуру, вы можете выбрать прямоугольную область которую хотите взять у текстуры:
sprite.setTextureRect(sf::IntRect(10, 10, 32, 32));
Можно изменить цвет спрайта. Цвет, который вы установите модулируется (умножается) с текстурой спрайта. Это также может быть использовано для изменения глобальной прозрачности (альфа-канал) спрайта.
Все эти спрайты использовали одну и ту же текстуру, но имеют разные цвета:
Спрайты также могут быть преобразованы: они имеют положение, ориентацию и масштаб.
// позиция sprite.setPosition(sf::Vector2f(10, 50)); // абсолютная позиция sprite.move(sf::Vector2f(5, 10)); // смещение относительно текущей позиции // поворот sprite.setRotation(90); // абсолютный угол sprite.rotate(15); // смещение относительно текущего угла // масштаб sprite.setScale(sf::Vector2f(0.5f, 2.f)); // абсолютный коэффициент масштабирования sprite.scale(sf::Vector2f(1.5f, 3.f)); // коэффициент масштабирования относительно текущего масштаба
По умолчанию, исходной точкой для этих трех преобразований является верхний левый угол спрайта. Если вы хотите установить исходную точку в другом месте (например в центре спрайта или в другом углу), вы можете использовать функцию setOrigin.
sprite.setOrigin(sf::Vector2f(25, 25));
Поскольку функции преобразования являются общими для всех объектов SFML, то они объясняются в отдельном уроке: Трансформация объектов.
Проблема белого квадрата
Вы успешно загрузили текстуру, правильно определили спрайт, и … всё то, что вы видите на экране это белый квадрат. Почему?
Это распространенная ошибка. Когда вы устанавливаете текстуру для спрайта, то всё что на самом деле происходит, это сохранение в спрайте указателя на экземпляр текстуры. Таким образом, если текстура уничтожается или перемещается в другое место в памяти, спрайт будет иметь неверный указатель текстуры.
Эта проблема возникает, когда вы пишете что-то наподобие этого:
sf::Sprite loadSprite(std::string filename) { sf::Texture texture; texture.loadFromFile(filename); return sf::Sprite(texture); } // ошибка: здесь уничтожается текстура
Вы должны правильно управлять временем жизни текстур, так, что бы они существовали так долго, как они используются спрайтами.
Важность использования такого малого числа текстур, какое только возможно
Такая стратегия хороша тем, что изменение текущей текстуры является дорогостоящей операцией для видеокарты. Техника рисования множества спрайтов, использующих одну и ту же текстуру, обеспечит лучшую производительность.
Кроме того, использование одной текстуры позволяет группировать неподвижные фигуры в единое целое (можно использовать только одну текстуру в вызове draw()), что позволит рисовать это целое намного быстрее, чем набор из множества отдельных объектов. Об этом подробнее будет рассказано в уроке Создание собственных объектов на основе массивов вершин(vertex arrays).
И так, имейте это в виду, когда вы создаёте свои изображения для анимации или свои тайлсеты: используйте одну текстуру, если это возможно.
Использование sf::Texture вместе с OpenGL
Если вы используете OpenGL, а не графические объекты SFML, вы всё равно можете использовать sf::Texture как обертку над текстурами в OpenGL и тем самым взаимодействовать с вашими объектами OpenGL.
Что бы включить sf::Texture для рисования (эквивалент glBindTexture()), необходимо вызвать статическую функцию bind():
sf::Texture texture; ... // привязка текстуры sf::Texture::bind(&texture); // рисуйте ваши текстурированные объекты OpenGL здесь... // "отвязка" текстуры sf::Texture::bind(NULL);
Here is a MWE
#include <SFML/Graphics.hpp>
#include <string>
#include <iostream>
int main(int argc, char *argv)
{
sf::RenderWindow window(sf::VideoMode(200, 200), "Title");
std::string image_filename;
image_filename = "image.png";
sf::Image image;
if(!image.loadFromFile(image_filename))
{
std::cout << "Error: Could not load file " << image_filename << std::endl;
}
sf::Texture texture;
texture.loadFromImage(image);
sf::Sprite sprite;
sprite.setTexture(texture);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(sprite);
window.display();
}
return 0;
}
When starting the application, the window looks like this:
After resizing the window, the window looks like this:
The global and local bounds of the sprite are the same before and after re-sizing.
The behaviour (as seen in the screenshot) is not what I would expect the default behaviour to be.
- The number of pixels (width and height) of the window has changed.
- The same region of the image/texture/sprite is drawn to the window.
- An interpolation algorithm of some kind has been applied to scale the initial window width and height to the resized window width and height.
Maybe that was already really obvious. This behaviour makes sense when designing something like a game**, but it doesn’t make sense for the application I am intending to implement.
**
(because the background will «scale» as the window is resized, whereas the rendering resolution would be fixed in advance, likely by a config file)
Before attempting to change the behaviour I want to understand why the behaviour is like this.
Presumably opening a RenderWindow
with some initial default resolution (width x height) starts some kind of rendering instance on the GPU with that width and height. Resizing the window on the other hand presumably changes that width and height of the window in the «window system» (window manager, regardless of which OS it is running on) but does not update or change the corresponding width and height on the GPU rendering instance.
Or at least this is my best guess as to what happens.
Is there a way to change this behaviour? Perhaps using something like a SFML RenderWindow is fundamentally the wrong approach to use?
Note: I checked and the «scale» of the sprite does not change during a resize operation. So my assumption would be that the initial window size fixes the resolution of the array of pixels to be rendered and then this is upscaled/downscaled to fit the size of the actual window when drawing pixel data into the display pixel buffer using some kind of interpolation algorithm.
Here is a MWE
#include <SFML/Graphics.hpp>
#include <string>
#include <iostream>
int main(int argc, char *argv)
{
sf::RenderWindow window(sf::VideoMode(200, 200), "Title");
std::string image_filename;
image_filename = "image.png";
sf::Image image;
if(!image.loadFromFile(image_filename))
{
std::cout << "Error: Could not load file " << image_filename << std::endl;
}
sf::Texture texture;
texture.loadFromImage(image);
sf::Sprite sprite;
sprite.setTexture(texture);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(sprite);
window.display();
}
return 0;
}
When starting the application, the window looks like this:
After resizing the window, the window looks like this:
The global and local bounds of the sprite are the same before and after re-sizing.
The behaviour (as seen in the screenshot) is not what I would expect the default behaviour to be.
- The number of pixels (width and height) of the window has changed.
- The same region of the image/texture/sprite is drawn to the window.
- An interpolation algorithm of some kind has been applied to scale the initial window width and height to the resized window width and height.
Maybe that was already really obvious. This behaviour makes sense when designing something like a game**, but it doesn’t make sense for the application I am intending to implement.
**
(because the background will «scale» as the window is resized, whereas the rendering resolution would be fixed in advance, likely by a config file)
Before attempting to change the behaviour I want to understand why the behaviour is like this.
Presumably opening a RenderWindow
with some initial default resolution (width x height) starts some kind of rendering instance on the GPU with that width and height. Resizing the window on the other hand presumably changes that width and height of the window in the «window system» (window manager, regardless of which OS it is running on) but does not update or change the corresponding width and height on the GPU rendering instance.
Or at least this is my best guess as to what happens.
Is there a way to change this behaviour? Perhaps using something like a SFML RenderWindow is fundamentally the wrong approach to use?
Note: I checked and the «scale» of the sprite does not change during a resize operation. So my assumption would be that the initial window size fixes the resolution of the array of pixels to be rendered and then this is upscaled/downscaled to fit the size of the actual window when drawing pixel data into the display pixel buffer using some kind of interpolation algorithm.