Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions graphics/include/gz/common/Image.hh
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ namespace gz
/// \brief Encapsulates an image
class GZ_COMMON_GRAPHICS_VISIBLE Image
{
/// \brief Image channel
public: enum 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
{
Expand Down Expand Up @@ -201,6 +214,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<unsigned char> 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
Expand Down
22 changes: 13 additions & 9 deletions graphics/src/AssimpLoader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ std::pair<std::string, ImagePtr> AssimpLoader::Implementation::LoadTexture(

std::pair<ImagePtr, ImagePtr>
AssimpLoader::Implementation::SplitMetallicRoughnessMap(
const Image& _img) const
const Image &_img) const
{
std::pair<ImagePtr, ImagePtr> ret;
// Metalness in B roughness in G
Expand All @@ -583,28 +583,32 @@ std::pair<ImagePtr, ImagePtr>
std::vector<unsigned char> metalnessData(width * height * bytesPerPixel);
std::vector<unsigned char> roughnessData(width * height * bytesPerPixel);

std::vector<unsigned char> metalnessData8bit = _img.ChannelData(Image::BLUE);
std::vector<unsigned char> roughnessData8bit = _img.ChannelData(Image::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
unsigned int colorB = metalnessData8bit[y * width + x];
unsigned int colorG = roughnessData8bit[y * width + x];
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;
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we also had a Image::SetChannelData function, we can use that instead of this nested for loop and avoid unnecessary memory allocations for the temporary std::vectors. Not sure how feasible that is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw that there's a FreeImage_SetChannel for helping to do this. But it requires converting the data back to FIBITMAP so not sure if it's more efficient. For now, I ticketed an issue for this #708

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, let's investigate this some other time then. I raised this just because I noticed the creation of the std::vectors was a significant part of the cost of this function, so I was hoping we would directly write to the Image object we create below.

}
}

// First is metal, second is rough
ret.first = std::make_shared<Image>();
ret.first->SetFromData(&metalnessData[0], width, height, Image::RGBA_INT8);
ret.second = std::make_shared<Image>();
ret.second->SetFromData(&roughnessData[0], width, height, Image::RGBA_INT8);

return ret;
}

Expand Down
45 changes: 45 additions & 0 deletions graphics/src/Image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -702,3 +702,48 @@ FIBITMAP* Image::Implementation::SwapRedBlue(const unsigned int &_width,

return copy;
}


//////////////////////////////////////////////////
std::vector<unsigned char> Image::ChannelData(Channel _channel) const
{
if ((this->dataPtr->imageType != FIT_BITMAP) ||
(this->dataPtr->colorType != FIC_RGB &&
this->dataPtr->colorType != FIC_RGBALPHA))
{
gzerr << "Extraction of channeld ata is only support for RGB[A] image"
<< std::endl;
return {};
}

FIBITMAP* channelData = nullptr;
switch (_channel)
{
case RED:
channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_RED);
break;
case GREEN:
channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_GREEN);
break;
case BLUE:
channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_BLUE);
break;
case ALPHA:
channelData = FreeImage_GetChannel(this->dataPtr->bitmap, FICC_ALPHA);
break;
default:
break;
}

if (!channelData)
{
gzerr << "Invalid input channel: " << _channel << std::endl;
return {};
}


FIBITMAP *tmp = FreeImage_ConvertTo8Bits(channelData);
std::vector<unsigned char> data = this->dataPtr->DataImpl(tmp);
FreeImage_Unload(tmp);
return data;
}
45 changes: 45 additions & 0 deletions graphics/src/Image_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,51 @@ 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<unsigned char> red = img.ChannelData(common::Image::RED);
std::vector<unsigned char> green = img.ChannelData(common::Image::GREEN);
std::vector<unsigned char> blue = img.ChannelData(common::Image::BLUE);
std::vector<unsigned char> alpha = img.ChannelData(common::Image::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<const char *, unsigned int, unsigned int>;

class ImagePerformanceTest : public ImageTest,
Expand Down
Loading