Using Boost.GIL to convert an image into “raw” bytes

后端 未结 4 1579
星月不相逢
星月不相逢 2021-01-06 09:53

Goal

I\'m trying to move towards Boost GIL to replace some similar functionality I\'ve implemented that is reaching the end of its maintainable life.

I have

相关标签:
4条回答
  • 2021-01-06 10:21

    I would just push_back each color of the rgb8_pixel_t 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 an expert on GIL either.

    0 讨论(0)
  • 2021-01-06 10:22

    Here's some 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]
     }
    

    It's only for grayscale, but presumably the coloured version is similar.

    0 讨论(0)
  • 2021-01-06 10:29

    I just ran into this same problem; here for possible future reference is the answer I can come up with now that I've solved it for myself:

    That copy_pixels approach is good. The only problem is the destination type. If you happen to know that rgb8_pixel_t is formatted in memory as if it were three consecutive uint8_t's, then all you need 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);
    

    That's copied from my project, more or less; I'm using a 32-bit image but it should work the same for any other single, hardcoded image type. (I haven't learned how to use the "any_" stuff from GIL, so I can't comment on dynamically determined image types.)

    In the code above, I somewhat crudely confirm that an rgba8_pixel_t is what OpenGL will see as an "INT_8_8_8_8" or whatever, by doing that static_assert. I think it's probably better to take that information from the GIL documentation than to make a guess and try to confirm it with an assertion, but I can't seem to find a clear statement of it there (I'm also new to GIL, so maybe I'm just missing it). But it seems fairly clear that this is part of the intention of the design of GIL's pixel types. For instance, the GIL design guide says at one point, "The most commonly used pixel is a homogeneous pixel whose values are together in memory." "Together in memory" seems to be just what I'm looking for. Just after that, the guide talks about "planar" pixel types, in which the color channel values for one pixel are NOT stored together in memory. It would be strange to go to all the trouble of supporting that distinction as carefully as they do, and then not actually bother to make the interleaved pixel type pack its color values tightly together in memory.

    Anyway, I've demonstrated in my own project that this approach works with at least the version of Boost that I'm using (1.57), and I claim that if a future version changes this, then my static_assert will almost certainly catch it.

    (Another approach to potentially fall back on would be to actually go ahead and use a planar pixel to map between your uint_8_t array and the rgb8_pixel_t that 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 isn't really superior to the at_c strategy. It's just kind of a neat example I guess. *_planar_ptr_t is surprisingly smart!)

    Also note that in today's C++ you don't have to make a separate type to capture the body of your "for each" loop; you can use an anonymous function, as above. (I wrap mine in std::function because I guess GIL does some internal copy-assignment of the function object, or something like that, and the compiler gets mad if a naked anonymous function is passed in. I guess the std::function wrapping may reduce efficiency a bit here; in my case that doesn't seem to be important.)

    0 讨论(0)
  • 2021-01-06 10:36

    A complete, simplified form of the actual code I ended up using is:

    #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(d.w, d.h);
        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);
        d.w = img.width();
        d.h = img.height();
    
        std::vector<uint8_t> storage;
        storage.reserve(d.w*d.h*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 originally 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'm not sure exactly what issue they fixed with the version of boost I had at the time, but they don't seem to be required with 1.48.

    0 讨论(0)
提交回复
热议问题