This thread is out-dated. Please use this one (http://en.sfml-dev.org/forums/index.php?topic=17191.msg123626).
Hi there :)
Introduction:
we already know that using few, large textures with multiple sprite animation frames is more efficient than using many small textures with single frames. I was looking - I guess as most other hobby developers, too - for suitable sprite graphics. Some assets are using the frameset-approach (multiple frames within a larger texture), some are using single files per frame. But if you want to avoid using a mixture of both approaches, you are forced to convert all graphics to one format either used by the first or used by the second approach.
But simple merging (potentially diffently sized!) frames to a set of uniform sized frames isn't great. It implies some wasted memory (because of many empty/transparent pixels inside the image). On the other hand, merging them memory-efficient by hand isn't great either. To solve this, I wrote some piece of code ;)
How it's working:
The idea is straight-forward: try to find an empty spot for your images. But this implies some more work! The basic idea is to add lots of images to some kind of container. On each insertion the container (with a fixed size) returns whether it had free space. If yes, the image was added and it's offset (topleft position within the container) will be returned call-by-reference to the second argument given to add.
Here's a small (runnable) example code.. It's creating some random single-colored images with randomized size, sorting it by the images' size (desceding) and adding largest images first. After that, the final image is written to disk as example usage.
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <utils/image_atlas.hpp> // <-- see below
int main() {
std::srand(std::time(0));
// create images --- ugly - feel free to skip while reading^^
std::size_t num = 40;
std::vector<sf::Image> images{num};
sf::Uint8 k = 0;
int i = 0;
for (auto& img: images) {
sf::Color c;
if (i < num/3) {
c = sf::Color{k, 0, 0};
} else if (i < 2*num/3) {
c = sf::Color{0, k, 0};
} else {
c = sf::Color{0, 0, k};
}
img.create(40 * (std::rand() % 10 + 1), 40 * (std::rand() % 10 + 1), c);
k += (255/(num/3));
++i;
if (k > 200) {
k = 40;
}
}
// sort by size (descending)
// --> helps to achieve a (at least nearly) well-constructed atlas
std::sort(images.begin(), images.end(), [&](sf::Image const & a, sf::Image const & b) {
auto size_a = a.getSize();
auto size_b = b.getSize();
return size_a.x * size_a.y > size_b.x * size_b.y;
});
// add to ImageAtlas -- here the actual work starts
ImageAtlas target{1024u}; // or use default ctor for max size
for (auto const & img: images) {
sf::Vector2u pos;
if (target.add(img, pos)) {
auto size = img.getSize();
std::cout << "Added image sized " << size.x << "x" << size.y
<< " added at (" << pos.x << "|" << pos.y << ")\n";
}
}
// generate final image
auto result = target.generate();
result.saveToFile("out.png");
}
By the way: The included header for the image_atlas can be found here: https://github.com/cgloeckner/SfmlExt
What to do with?
The image atlas can be create once for the current machine (atlas' size might depend on the hardware's limitations), write it to disk and load it when starting the game. Of course you'll also need to store those initial-image-and-its-offset-relations to keep atlas and mapping consistent.
Possibly, the (resulting) texture and rectangles (computed offset with initial frame's size) could be used by Thor's animation handling system ;)
About the code:
It poorly documented, yet. Also some approaches (e.g. searching a free spot) are not that great... But it's working in the first place ;)
Please feel free to feedback!
Kind regards
Glocke
/EDIT: Some productive example: These offsets should be stored (mapping!) for the example frameset (graphics taken from "Battle for Wesnoth")
Added image sized 72x102 added at (0|0)
Added image sized 72x102 added at (72|0)
Added image sized 72x102 added at (144|0)
Added image sized 72x102 added at (216|0)
Added image sized 72x102 added at (288|0)
Added image sized 72x102 added at (360|0)
Added image sized 72x102 added at (432|0)
Added image sized 72x100 added at (0|102)
Added image sized 72x100 added at (72|102)
Added image sized 72x100 added at (144|102)
Added image sized 72x100 added at (216|102)
Added image sized 94x76 added at (288|102)
Added image sized 94x76 added at (382|102)
Added image sized 82x72 added at (288|178)
Added image sized 78x72 added at (370|178)
Added image sized 72x72 added at (0|202)
Added image sized 72x72 added at (72|202)
Added image sized 72x72 added at (144|202)
Added image sized 72x72 added at (216|202)
Added image sized 72x72 added at (288|250)
Added image sized 72x72 added at (360|250)
Added image sized 72x72 added at (432|250)
Added image sized 72x72 added at (0|274)
Added image sized 72x72 added at (72|274)
Added image sized 72x72 added at (144|274)
Added image sized 72x72 added at (216|274)
Added image sized 72x72 added at (288|322)
Added image sized 72x72 added at (360|322)
Added image sized 72x72 added at (432|322)
Added image sized 72x72 added at (0|346)
This is great! I was almost writing something like this myself, but you saved me a lot of time. Followed as well, because I'll be needing it in the future. :D
That's great to hear! 8)
If there's something missing to the atlas, feel free to mention it :)
/EDIT: I just built another example using Thor for animation handling.
Extract from the example (full example: https://github.com/cgloeckner/SfmlExt/blob/master/atlas_example.cpp (https://github.com/cgloeckner/SfmlExt/blob/master/atlas_example.cpp)) using a customizable MyPacker class
// Create Image Atlas
MyPacker packer;
for (auto i = 1u; i <= 5; ++i) {
packer.addImage("wraith-s-attack-"+std::to_string(i)+".png");
}
auto atlas = packer.generate(256u);
atlas.saveToFile("out.png");
// Create Animation
thor::FrameAnimation attack;
for (auto i = 1u; i <= 5; ++i) {
std::string fname{"wraith-s-attack-"+std::to_string(i)+".png"};
attack.addFrame(1.f, static_cast<sf::IntRect>(packer.getClipping(fname)));
}
thor::Animator<sf::Sprite, std::string> animator;
animator.addAnimation("attack", attack, sf::milliseconds(625)); // 5*125ms
Without such a mapping, the atlas itself isn't of much help.
The mapping is given by bool ImageAtlas::add(sf::Image const & image, sf::Vector2u& pos)
when querying the (call-by-ref-passed) pos argument after a successful add.
When sorting, you should track some kind of information to identify the images (e.g. by filename). View the latest example for this: When adding a pair of std::string and sf::Image, the image can be identified by it's previous filename. The MyPacker implementation also provides querying clipping rectangle by the original filename of each frame which was added.
/EDIT: But yes, I'll need to provide a (more or less generic) packer implementation (generic referring to the identification of the image). Scheduled! 8)