Using Boost.GIL to Convert Images to Raw Bytes

goal

I am trying to switch to Boost GIL to replace some of the similar functionality that I implemented that reach the end of a supported life.

I have existing code that works with 24 BPP, 8-bit RGB images using uint8_t* . I cannot change this because the same interface is used to display images from different places (for example, OpenGL buffers), and there is already a lot of code there.

So I try to use the GIL in small steps, starting from reading the file and copying the bytes by bytes to std::vector<uint8_t> , which I can use to manage the storage, but still get uint8_t* using &vector[0] .

This can be omitted transparently behind existing interfaces until it reaches the refactoring point.

What i tried

I thought this should be a simple case of using copy_pixels() with two matching lines.

I put together a minimal, complete example that illustrates the sum of what I was able to achieve by looking at the documents and trying:

 #include <boost/gil/rgb.hpp> #include <boost/gil/extension/io/png_dynamic_io.hpp> #include <stdint.h> #include <vector> int main() { std::vector<uint8_t> storage; { using namespace boost::gil; rgb8_image_t img; png_read_image("test.png", img); // what should replace 3 here to be more "generic"? storage.resize(img.width()*img.height()*3); // doesn't work, the type of the images aren't compatible. copy_pixels(const_view(img), interleaved_view(img.width(), img.height(), &storage[0], 3*img.width())); } } 

Where am i stuck

This will not compile:

 error: cannot convert 'const boost::gil::pixel<unsigned char, boost::gil::layout<boost::mpl::vector3<boost::gil::red_t, boost::gil::green_t, boost::gil::blue_t> > >' to 'unsigned char' in assignment 

This is pretty clear: an RGB pixel cannot be converted to one unsigned char automatically. I thought that I would try using copy_and_convert_pixels() to fix this, but I can’t see the path around 3: 1 (i.e. I have 3 unsigned char in the output for each pixel in the original image), the problem with these conversions. The conversion seems to be more focused on color conversion (e.g. RGB-> HSV) or packaging changes.

+4
source share
4 answers

I would just push_back each rgb8_pixel_t color individually:

 struct PixelInserter{ std::vector<uint8_t>* storage; PixelInserter(std::vector<uint8_t>* s) : storage(s) {} void operator()(boost::gil::rgb8_pixel_t p) const { storage->push_back(boost::gil::at_c<0>(p)); storage->push_back(boost::gil::at_c<1>(p)); storage->push_back(boost::gil::at_c<2>(p)); } }; int main() { std::vector<uint8_t> storage; { using namespace boost::gil; rgb8_image_t img; png_read_image("test.png", img); storage.reserve(img.width() * img.height() * num_channels<rgb8_image_t>()); for_each_pixel(const_view(img), PixelInserter(&storage)); } ... } 

... but I'm not a GIL specialist either.

+3
source

I ran into this problem; here for a possible future reference - this is the answer that I can come up with now that I have decided this for myself:

This copy_pixels method is good. The only problem is the type of destination. If you know that rgb8_pixel_t is formatted in memory, as if it were three consecutive uint8_t, then all you have to do is something like this:

 boost::gil::rgba8_image_t image; boost::gil::png_read_image(filePath, image); auto view = boost::gil::view(image); typedef decltype(view)::value_type pixel; static_assert(sizeof(pixel) == 4, "the glTexImage2D call below assumes this"); pixel imageData[view.width() * view.height()]; boost::gil::copy_pixels(view, boost::gil::interleaved_view(view.width(), view.height(), imageData, view.width() * sizeof(pixel))); gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, imageData); 

This is copied from my project, more or less; I use a 32-bit image, but it should work the same for any other type of hard coded image. (I have not learned how to use the "any_" material from the GIL, so I cannot comment on dynamically defined types of images.)

In the above code, I am somewhat crudely confirming that rgba8_pixel_t is what OpenGL sees as "INT_8_8_8_8" or something else by doing this static_assert. I think it is probably better to take this information from the GIL documentation than to guess and try to confirm it with a statement, but I cannot find a clear statement about it (I am also new to GIL, so maybe I just skipped this) . But it seems pretty obvious that this is part of the design intent of the GIL pixel types. For example, the GIL design guide says at one point: “The most commonly used pixel is a uniform pixel whose values ​​are in memory.” “Together in memory” seems to be exactly what I'm looking for. Immediately after this, the manual talks about “flat” types of pixels in which the color channel values ​​for one pixel are NOT stored together in memory. It would be strange to go into all the problems in order to maintain this difference as carefully as they do, and then not actually worry about alternating pixel types packing its color values ​​together in memory.

In any case, I demonstrated in my own project that this approach works with at least the version of Boost that I use (1.57), and argue that if a future version changes this value, then my static_assert will almost certainly catch it.

(Another approach to potential return is actually to switch to using a planar pixel to match between your uint_8_t and rgb8_pixel_t array, which for_each_pixel gives you:

 boost::gil::rgba8_image_t image; boost::gil::png_read_image(filePath, image); auto view = boost::gil::view(image); uint8_t data[view.width() * view.height() * view.num_channels()]; using boost::gil::rgba8_pixel_t; uint8_t* cursor = data; boost::gil::for_each_pixel(view, std::function<void(rgba8_pixel_t)>( [&cursor](rgba8_pixel_t pixel) { boost::gil::rgba8_planar_ptr_t pixelInData(cursor++, cursor++, cursor++, cursor++); *pixelInData = pixel; // if data were an array of rgba8_pixel_t's, then we could just do this and be done with it: // *cursor++ = pixel; // (but in that case we might as well use copy_pixels!) })); gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data); 

But this is not much better than the at_c strategy. I think this is just a great example. * _planar_ptr_t amazingly smart!)

Also note that in today's C ++ you do not need to make a separate type to capture the body of your loop "for each"; you can use anonymous function as above. (I end mine in std :: function because I assume that the GIL does the internal assignment of a copy of the function object or something like that, and the compiler goes crazy if a naked anonymous function is passed. I assume that std :: function packaging may slightly decrease efficiency, in my case this does not seem important.)

+1
source

Here is the code I once used:

  unsigned char * buf = new unsigned char[w * h]; boost::gil::gray8_view_t image = boost::gil::interleaved_view(w, h, (boost::gil::gray8_pixel_t*)buf, w); for (size_t i = 0; i < ...; ++i) { boost::gil::gray8_view_t::x_iterator it = image.row_begin(i); // use it[j] to access pixel[i][j] } 

This is only for shades of gray, but apparently the color version is similar.

0
source

The full, simplified form of the actual code that I used is as follows:

 #include <boost/gil/rgb.hpp> #include <boost/gil/extension/io/png_dynamic_io.hpp> #include <vector> #include <string> #include <cstdint> struct dimension { int w,h; }; namespace { struct PixelInserter { std::vector<uint8_t>* storage; PixelInserter(std::vector<uint8_t>* s) : storage(s) {} void operator()(boost::gil::rgb8_pixel_t p) const { using boost::gil::at_c; storage->push_back(at_c<0>(p)); storage->push_back(at_c<1>(p)); storage->push_back(at_c<2>(p)); } }; // This could probably share code with the PixelInserter struct PixelWriter { const uint8_t *pixels; PixelWriter(const uint8_t *pixels) : pixels(pixels) {} void operator()(boost::gil::rgb8_pixel_t& p) { using boost::gil::at_c; at_c<0>(p) = *pixels++; at_c<1>(p) = *pixels++; at_c<2>(p) = *pixels++; } }; } void savePNG(const std::string& filename, const uint8_t *pixels, const dimension& d) { boost::gil::rgb8_image_t img(dw, dh); for_each_pixel(view(img), PixelWriter(pixels)); boost::gil::png_write_view(filename, view(img)); } std::vector<uint8_t> readPNG(const std::string& fn, dimension& d) { boost::gil::rgb8_image_t image_type; image_type img; png_read_image(fn, img); dw = img.width(); dh = img.height(); std::vector<uint8_t> storage; storage.reserve(dw*dh*boost::gil::num_channels<image_type>()); for_each_pixel(const_view(img), PixelInserter(&storage)); return storage; } int main(int argc, char **argv) { dimension d; const std::vector<uint8_t> pixels = readPNG(argv[1], d); savePNG(argv[2], &pixels[0], d); } 

I initially had the following before I included any of the GIL headers:

 #define png_infopp_NULL (png_infopp)NULL #define int_p_NULL (int*)NULL 

I don’t know exactly what problem they fixed with the upgrade version I had at that time, but they do not seem to be required from 1.48.

0
source

Source: https://habr.com/ru/post/1383486/


All Articles