Initializing std :: vector with repeating pattern
I'm working with OpenGL at the moment creating a "texture cache" that handles loading images and buffering them with OpenGL. If the image file cannot be loaded, it should revert to the default texture, which I hardcoded in the constructor.
I basically need to create a solid color texture. It's not too complicated, it's just an array of Pixels * Color Channels.
I am currently using std :: vector to store raw data before loading it with OpenGL. The problem I'm running into is that I can't find any information on how best to initialize a vector with a repeating pattern.
The first way I came up with is to use a loop.
std::vector<unsigned char> blue_texture;
for (int iii = 0; iii < width * height; iii++)
{
blue_texture.push_back(0);
blue_texture.push_back(0);
blue_texture.push_back(255);
blue_texture.push_back(255);
}
However, this seems inefficient, as the vector will have to be resized many times. Even if I reserve space first and loop through it is still inefficient as the content will be zeroed out before the loop, which means there are two entries for each unsigned char.
I am currently using the following method:
struct colour {unsigned char r; unsigned char g; unsigned char b; unsigned char a;};
colour blue = {0, 0, 255, 255};
std::vector<colour> texture((width * height), blue);
Then I fetch the data using:
reinterpret_cast<unsigned char*>(texture.data());
Is there a better way than this? I'm new to C / C ++ and I'll be honest, casting pointers scares me.
source to share
Solving your loop is the right way in my opinion. To make it efficient by removing duplicate calls to realloc useblue_texture.reserve(width * height * 4)
The fallback call will increase the allocation, that is, capacity of that size without zero padding. (Note that the operating system can still zero it if it pulls memory out of mmap, for example.) It doesn't resize the vector, so push_back and friends still work the same.
source to share
You can use reserve
to preselect a vector; this will avoid redistribution. You can also define a small sequence (probably a C style vector:
char const init[] = { 0, 0, 255, 255 };
and inserting a loop at the end of the vector:
for ( int i = 0; i < pixelCount; ++ i ) {
v.insert( v.end(), std::begin( init ), std::end( init ) );
}
this is only marginally more efficient than using four push_back
in a loop, but more concise and perhaps gives a clearer idea of ββwhat you are doing, albeit marginally: a big advantage could be the name for the initialization sequence (e.g. something like defaultBackground
) ...
source to share
The most efficient way is the least work.
Unfortunately push_back (), insert (), etc. must maintain the size () of vectors as they run, which are redundant operations when executed in a closed loop.
Therefore, the most efficient way is to allocate memory once and then copy the data directly into it without storing any other variables.
Made like this:
#include <iostream>
#include <array>
#include <vector>
using colour_fill = std::array<uint8_t, 4>;
using pixel_map = std::vector<uint8_t>;
pixel_map make_colour_texture(size_t width, size_t height, colour_fill colour)
{
// allocate the buffer
std::vector<uint8_t> pixels(width * height * sizeof(colour_fill));
auto current = pixels.data();
auto last = current + pixels.size();
while (current != last) {
current = std::copy(begin(colour), end(colour), current);
}
return pixels;
}
auto main() -> int
{
colour_fill blue { 0, 0, 255, 255 };
auto blue_bits = make_colour_texture(100, 100, blue);
return 0;
}
source to share
I would reserve all the required size and then use the insert function to re-add the template to the vector.
std::array<unsigned char, 4> pattern{0, 0, 255, 255};
std::vector<unsigned char> blue_texture;
blue_texture.reserve(width * height * 4);
for (int i = 0; i < (width * height); ++i)
{
blue_texture.insert(blue_texture.end(), pattern.begin(), pattern.end());
}
source to share
I made this template function that will change its input container to contain count
what it already contains.
#include <iostream>
#include <vector>
#include <algorithm>
template<typename Container>
void repeat_pattern(Container& data, std::size_t count) {
auto pattern_size = data.size();
if(count == 0 or pattern_size == 0) {
return;
}
data.resize(pattern_size * count);
const auto pbeg = data.begin();
const auto pend = std::next(pbeg, pattern_size);
auto it = std::next(data.begin(), pattern_size);
for(std::size_t k = 1; k < count; ++k) {
std::copy(pbeg, pend, it);
std::advance(it, pattern_size);
}
}
template<typename Container>
void show(const Container& data) {
for(const auto & item : data) {
std::cout << item << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> v{1, 2, 3, 4};
repeat_pattern(v, 3);
// should show three repetitions of times 1, 2, 3, 4
show(v);
}
Output (compiled as g++ example.cpp -std=c++14 -Wall -Wextra
):
1 2 3 4 1 2 3 4 1 2 3 4
source to share