使用Boost.GIL将图像转换为“原始”字节

时间:2011-11-28 18:33:21

标签: c++ image boost boost-gil

目标

我正在尝试使用Boost GIL来取代我已实现的一些类似功能,这些功能已达到其可维护生命的终点。

我现有的代码可以使用uint8_t*处理24个BPP,8位RGB图像。我不能改变它,因为相同的界面用于暴露来自不同地方的图​​像(例如OpenGL缓冲区),并且已经有相当多的代码。

因此我试图以小步骤使用GIL,首先读取一个文件,然后逐字节地将像素复制到std::vector<uint8_t>中,我可以使用它来管理存储,但仍然得到{{1使用uint8_t*

这可以透明地放在现有接口之后,直到重构有意义为止。

我尝试了什么

我认为这应该是使用copy_pixels()和两个适当构造的视图的简单情况。

我已经整理了一个最小的,完整的例子,通过查看文档和尝试来说明我能够实现的目标总和:

&vector[0]

我被困的地方

这不编译:

#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()));
  }
}

这是相当不言自明的 - RGB像素无法自动转换为单个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 。我以为我会尝试使用copy_and_convert_pixels()来解决这个问题,但是我无法看到3:1的方式(即我在源图像中的每个像素的输出中都有3个unsigned char s )这些转换的问题。转换似乎更多地针对色彩空间转换(例如RGB-> HSV)或打包更改。

4 个答案:

答案 0 :(得分:3)

我只是单独推回rgb8_pixel_t的每种颜色:

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));
  }
...
}

......但我也不是GIL的专家。

答案 1 :(得分:1)

我刚遇到同样的问题;这里可能的未来参考是我现在可以想出的答案,我已经为自己解决了这个问题:

copy_pixels方法很好。唯一的问题是目的地类型。如果您碰巧知道rgb8_pixel_t在内存中的格式就像连续三次uint8_t一样,那么您需要做的就是这样:

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);

这或多或少地从我的项目中复制了;我使用的是32位图像,但对于任何其他单一的硬编码图像类型,它应该可以正常工作。 (我还没有学会如何使用GIL中的任何_&#34;所以我无法评论动态确定的图像类型。)

在上面的代码中,我有点粗略地确认一个rgba8_pixel_t是OpenGL将看到的&#34; INT_8_8_8_8&#34;或者其他什么,通过执行static_assert。我认为从GIL文档中获取信息可能比猜测并尝试用断言确认它更好,但我似乎无法在那里找到明确的声明(I&# 39; m也是GIL的新手,所以也许我只是错过了它。但很明显,这是GIL像素类型设计意图的一部分。例如,GIL设计指南在某一点说,&#34;最常用的像素是均匀像素,其值在存储器中。&#34; &#34;在记忆中一起&#34;似乎正是我正在寻找的东西。就在那之后,指南讲述了平面&#34;像素类型,其中一个像素的颜色通道值不会一起存储在内存中。如果像他们那样仔细地支持这种区别,那就很奇怪了,然后实际上并没有费心去交错的像素类型将其颜色值紧密地组合在内存中。

无论如何,我已经在我自己的项目中证明了这种方法至少适用于我使用的Boost版本(1.57),我声称如果未来的版本改变了这一点,那么我的static_assert几乎肯定会抓住它。

(另一种可能依赖的方法是实际继续使用平面像素在你的uint_8_t数组和for_each_pixel给你的rgb8_pixel_t之间进行映射:

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);

但这并不比at_c策略优越。我猜这只是一个很好的例子。 * _planar_ptr_t非常聪明!)

另请注意,在今天的C ++中,您不必制作单独的类型来捕捉&#34;对于每个&#34;环;你可以使用匿名函数,如上所述。 (我在std :: function中包装我的因为我猜GIL对函数对象进行了一些内部的复制赋值,或类似的东西,如果传入一个裸匿名函数,编译器会生气。我想std :: function包装可能会在这里降低效率;在我的情况下,这看起来并不重要。)

答案 2 :(得分:0)

以下是我曾经使用的一些代码:

 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]
 }

仅适用于灰度,但可能是彩色版本相似。

答案 3 :(得分:0)

我最终使用的实际代码的完整,简化形式是:

#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);
}

在我添加任何GIL标题之前,我最初有以下内容:

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

我不确定他们当时使用的增强版本确定了什么问题,但是它们似乎不需要1.48。