diff --git a/graphics/include/gz/common/Image.hh b/graphics/include/gz/common/Image.hh index 69d8019a..40a6917a 100644 --- a/graphics/include/gz/common/Image.hh +++ b/graphics/include/gz/common/Image.hh @@ -62,6 +62,19 @@ namespace gz /// \brief Encapsulates an image class GZ_COMMON_GRAPHICS_VISIBLE Image { + /// \brief Image channel + public: enum class Channel + { + /// \brief Red channel + RED = 0, + /// \brief Green channel + GREEN = 1, + /// \brief Blue channel + BLUE = 2, + /// \brief Alpha channel + ALPHA = 3 + }; + /// \brief Pixel formats enumeration public: enum PixelFormatType { @@ -199,6 +212,12 @@ namespace gz /// \return true if image has a bitmap public: bool Valid() const; + /// \brief Extract a single channel (red, green, blue, or alpha) from + /// an RGB[A] image and return a single channel 8 bit image data. + /// \param[in] _channel Channel to extract + /// \return 8 bit single channel image data + public: std::vector ChannelData(Channel _channel) const; + /// \brief Convert a single channel image data buffer into an RGB image. /// During the conversion, the input image data are normalized to 8 bit /// values i.e. [0, 255]. Optionally, specify min and max values to use diff --git a/graphics/src/AssimpLoader.cc b/graphics/src/AssimpLoader.cc index 40f4685e..4913fde2 100644 --- a/graphics/src/AssimpLoader.cc +++ b/graphics/src/AssimpLoader.cc @@ -572,7 +572,7 @@ std::pair AssimpLoader::Implementation::LoadTexture( std::pair AssimpLoader::Implementation::SplitMetallicRoughnessMap( - const Image& _img) const + const Image &_img) const { std::pair ret; // Metalness in B roughness in G @@ -583,20 +583,26 @@ std::pair std::vector metalnessData(width * height * bytesPerPixel); std::vector roughnessData(width * height * bytesPerPixel); + std::vector metalnessData8bit = + _img.ChannelData(Image::Channel::BLUE); + std::vector roughnessData8bit = + _img.ChannelData(Image::Channel::GREEN); for (unsigned int y = 0; y < height; ++y) { for (unsigned int x = 0; x < width; ++x) { // RGBA so 4 bytes per pixel, alpha fully opaque - auto baseIndex = bytesPerPixel * (y * width + x); - auto color = _img.Pixel(x, (height - y - 1)); - metalnessData[baseIndex] = color.B() * 255.0; - metalnessData[baseIndex + 1] = color.B() * 255.0; - metalnessData[baseIndex + 2] = color.B() * 255.0; + unsigned int idx = y * width + x; + unsigned int colorB = metalnessData8bit[idx]; + unsigned int colorG = roughnessData8bit[idx]; + auto baseIndex = bytesPerPixel * idx; + metalnessData[baseIndex] = colorB; + metalnessData[baseIndex + 1] = colorB; + metalnessData[baseIndex + 2] = colorB; metalnessData[baseIndex + 3] = 255; - roughnessData[baseIndex] = color.G() * 255.0; - roughnessData[baseIndex + 1] = color.G() * 255.0; - roughnessData[baseIndex + 2] = color.G() * 255.0; + roughnessData[baseIndex] = colorG; + roughnessData[baseIndex + 1] = colorG; + roughnessData[baseIndex + 2] = colorG; roughnessData[baseIndex + 3] = 255; } } diff --git a/graphics/src/Image.cc b/graphics/src/Image.cc index 732401ef..2794184b 100644 --- a/graphics/src/Image.cc +++ b/graphics/src/Image.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -700,3 +701,47 @@ FIBITMAP* Image::Implementation::SwapRedBlue(const unsigned int &_width, return copy; } + +////////////////////////////////////////////////// +std::vector Image::ChannelData(Channel _channel) const +{ + if ((this->dataPtr->imageType != FIT_BITMAP) || + (this->dataPtr->colorType != FIC_RGB && + this->dataPtr->colorType != FIC_RGBALPHA)) + { + gzerr << "Extraction of channel data is only support for RGB[A] images" + << std::endl; + return {}; + } + + FIBITMAP* channelData = nullptr; + switch (_channel) + { + case Channel::RED: + channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_RED); + break; + case Channel::GREEN: + channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_GREEN); + break; + case Channel::BLUE: + channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_BLUE); + break; + case Channel::ALPHA: + channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_ALPHA); + break; + default: + break; + } + + if (!channelData) + { + gzerr << "Invalid input channel: " << static_cast(_channel) + << std::endl; + return {}; + } + + FIBITMAP *tmp = FreeImage_ConvertTo8Bits(channelData); + std::vector data = this->dataPtr->DataImpl(tmp); + FreeImage_Unload(tmp); + return data; +} diff --git a/graphics/src/Image_TEST.cc b/graphics/src/Image_TEST.cc index 9c8ae9c1..c74cb825 100644 --- a/graphics/src/Image_TEST.cc +++ b/graphics/src/Image_TEST.cc @@ -729,6 +729,55 @@ TEST_F(ImageTest, Color16bit) } } +///////////////////////////////////////////////// +TEST_F(ImageTest, ChannelData) +{ + // load image, extra data from each channel and verify values + common::Image img; + ASSERT_EQ(0, img.Load(kTestData)); + ASSERT_TRUE(img.Valid()); + + std::vector red = + img.ChannelData(common::Image::Channel::RED); + std::vector green = + img.ChannelData(common::Image::Channel::GREEN); + std::vector blue = + img.ChannelData(common::Image::Channel::BLUE); + std::vector alpha = + img.ChannelData(common::Image::Channel::ALPHA); + + EXPECT_FALSE(red.empty()); + ASSERT_EQ(img.Width() * img.Height(), red.size()); + ASSERT_EQ(red.size(), green.size()); + ASSERT_EQ(red.size(), blue.size()); + ASSERT_EQ(red.size(), alpha.size()); + + for (auto i = 0u; i < img.Height(); ++i) + { + for (auto j = 0u; j < img.Width(); ++j) + { + unsigned int idx = i * img.Width() + j; + unsigned int r = red[idx]; + unsigned int g = green[idx]; + unsigned int b = blue[idx]; + unsigned int a = alpha[idx]; + + ASSERT_EQ(0u, g); + ASSERT_EQ(255u, a); + if (j < 80) + { + ASSERT_EQ(255u, r); + ASSERT_EQ(0u, b); + } + else + { + ASSERT_EQ(0u, r); + ASSERT_EQ(255u, b); + } + } + } +} + using string_int2 = std::tuple; class ImagePerformanceTest : public ImageTest,