Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add function to convert single channel image data to RGB image #205

Merged
merged 7 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
75 changes: 75 additions & 0 deletions graphics/include/ignition/common/Image.hh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifndef IGNITION_COMMON_IMAGE_HH_
#define IGNITION_COMMON_IMAGE_HH_

#include <limits>
#include <memory>
#include <string>
#include <vector>
Expand Down Expand Up @@ -185,6 +186,80 @@ namespace ignition
/// \return true if image has a bitmap
public: bool Valid() 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
/// when normalizing the input image data. For example, if min and max
/// are set to 1 and 10, a data value 2 will be normalized to:
/// (2 - 1) / (10 - 1) * 255.
/// \param[in] _data input image data buffer
/// \param[in] _width image width
/// \param[in] _height image height
/// \param[out] _output Output RGB image
/// \param[in] _min Minimum value to be used when normalizing the input
/// image data to RGB.
/// \param[in] _max Maximum value to be used when normalizing the input
/// image data to RGB.
/// \param[in] _flip True to flip the values after normalization, i.e.
/// lower values are converted to brigher pixels.
public: template<typename T>
static void ConvertToRGBImage(const void *_data,
unsigned int _width, unsigned int _height, Image &_output,
T _min = std::numeric_limits<T>::max(),
T _max = std::numeric_limits<T>::lowest(), bool _flip = false)
{
unsigned int samples = _width * _height;
unsigned int bufferSize = samples * sizeof(T);

auto buffer = std::vector<T>(samples);
memcpy(buffer.data(), _data, bufferSize);

auto outputRgbBuffer = std::vector<uint8_t>(samples * 3);

// use min and max values found in the data if not specified
T min = std::numeric_limits<T>::max();
T max = std::numeric_limits<T>::lowest();
if (_min > max)
{
for (unsigned int i = 0; i < samples; ++i)
{
auto v = buffer[i];
// ignore inf values when computing min/max
// cast to float when calling isinf to avoid compile error on
// windows
if (v > max && !std::isinf(static_cast<float>(v)))
max = v;
if (v < min && !std::isinf(static_cast<float>(v)))
min = v;
}
}
min = math::equal(_min, std::numeric_limits<T>::max()) ? min : _min;
max = math::equal(_max, std::numeric_limits<T>::lowest()) ? max : _max;

// convert to rgb image
// color is grayscale, i.e. r == b == g
double range = static_cast<double>(max - min);
if (ignition::math::equal(range, 0.0))
range = 1.0;
unsigned int idx = 0;
for (unsigned int j = 0; j < _height; ++j)
{
for (unsigned int i = 0; i < _width; ++i)
{
auto v = buffer[idx++];
double t = static_cast<double>(v - min) / range;
if (_flip)
t = 1.0 - t;
uint8_t r = static_cast<uint8_t>(255*t);
unsigned int outIdx = j * _width * 3 + i * 3;
outputRgbBuffer[outIdx] = r;
outputRgbBuffer[outIdx + 1] = r;
outputRgbBuffer[outIdx + 2] = r;
}
}
_output.SetFromData(outputRgbBuffer.data(), _width, _height, RGB_INT8);
}

IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING
/// \brief Private data pointer
private: std::unique_ptr<ImagePrivate> dataPtr;
Expand Down
198 changes: 198 additions & 0 deletions graphics/src/Image_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,204 @@ TEST_F(ImageTest, ConvertPixelFormat)
Image::ConvertPixelFormat("BAYER_BGGR8"));
}

/////////////////////////////////////////////////
TEST_F(ImageTest, ConvertToRGBImage)
{
unsigned int width = 8;
unsigned int height = 8;
unsigned int size = width * height;

// test L_INT8 format
{
// create sample image data for testing
// the image is divided into 4 sections from top to bottom
// The values in the sections are 10, 20, 30, 40
auto buffer = std::vector<uint8_t>(size);
for (unsigned int i = 0; i < height; ++i)
{
uint8_t v = 10 * static_cast<int>(i / (width/ 4.0) + 1);
for (unsigned int j = 0; j < width; ++j)
{
buffer[i*width + j] = v;
}
}

common::Image output;
common::Image::ConvertToRGBImage<uint8_t>(
buffer.data(), width, height, output);

// Check RGBA data
unsigned char *data = nullptr;
unsigned int outputSize = 0;
output.Data(&data, outputSize);
EXPECT_EQ(size * 3, outputSize);
ASSERT_NE(nullptr, data);

for (unsigned int i = 0u; i < height; ++i)
{
for (unsigned int j = 0u; j < width; ++j)
{
unsigned int r = data[i * width * 3 + j * 3];
unsigned int g = data[i * width * 3 + j * 3 + 1];
unsigned int b = data[i * width * 3 + j * 3 + 2];
EXPECT_EQ(r, g);
EXPECT_EQ(r, b);
if (i < (height / 4.0))
EXPECT_EQ(0u, r);
else if (i >= (height / 4.0) && i < (height / 2.0))
EXPECT_EQ(static_cast<unsigned int>(255 / 3), r);
else if (i >= (height / 2.0) && i < (height / 4.0 * 3.0))
EXPECT_EQ(static_cast<unsigned int>(255 / 3 * 2), r);
else
EXPECT_EQ(255u, r);
}
}
}

// test L_INT16 format
{
// create sample image data for testing
// the image is divided into 4 sections from top to bottom
// The values in the sections are 100, 200, 300, 400
auto buffer = std::vector<uint16_t>(size);
for (unsigned int i = 0; i < height; ++i)
{
uint16_t v = 100 * static_cast<int>(i / (height / 4.0) + 1);
for (unsigned int j = 0; j < width; ++j)
{
buffer[i*width + j] = v;
}
}

common::Image output;
common::Image::ConvertToRGBImage<uint16_t>(
buffer.data(), width, height, output);

// Check RGB data
unsigned char *data = nullptr;
unsigned int outputSize = 0;
output.Data(&data, outputSize);
EXPECT_EQ(size * 3, outputSize);
ASSERT_NE(nullptr, data);

for (unsigned int i = 0u; i < height; ++i)
{
for (unsigned int j = 0u; j < width; ++j)
{
unsigned int r = data[i * width * 3 + j * 3];
unsigned int g = data[i * width * 3 + j * 3 + 1];
unsigned int b = data[i * width * 3 + j * 3 + 2];
EXPECT_EQ(r, g);
EXPECT_EQ(r, b);

if (i < (height / 4.0))
EXPECT_EQ(0u, r);
else if (i >= (height / 4.0) && i < (height / 2.0))
EXPECT_EQ(static_cast<unsigned int>(255 / 3), r);
else if (i >= (height / 2.0) && i < (height / 4.0 * 3.0))
EXPECT_EQ(static_cast<unsigned int>(255 / 3 * 2), r);
else
EXPECT_EQ(255u, r);
}
}
}

// test R_FLOAT32 format
{
// create sample image data for testing
// the image is divided into 4 sections from top to bottom
// The values in the sections are 0.5, 1.0, 1.5, 2.0
auto buffer = std::vector<float>(size);
for (unsigned int i = 0; i < height; ++i)
{
float v = 0.5f * static_cast<int>(i / (height / 4.0) + 1);
for (unsigned int j = 0; j < width; ++j)
{
buffer[i*width + j] = v;
}
}

common::Image output;
common::Image::ConvertToRGBImage<float>(
buffer.data(), width, height, output);

// Check RGB data
unsigned char *data = nullptr;
unsigned int outputSize = 0;
output.Data(&data, outputSize);
EXPECT_EQ(size * 3, outputSize);
ASSERT_NE(nullptr, data);

for (unsigned int i = 0u; i < height; ++i)
{
for (unsigned int j = 0u; j < width; ++j)
{
unsigned int r = data[i * width * 3 + j * 3];
unsigned int g = data[i * width * 3 + j * 3 + 1];
unsigned int b = data[i * width * 3 + j * 3 + 2];
EXPECT_EQ(r, g);
EXPECT_EQ(r, b);

if (i < (height / 4.0))
EXPECT_EQ(0u, r);
else if (i >= (height / 4.0) && i < (height / 2.0))
EXPECT_EQ(static_cast<unsigned int>(255 / 3), r);
else if (i >= (height / 2.0) && i < (height / 4.0 * 3.0))
EXPECT_EQ(static_cast<unsigned int>(255 / 3 * 2), r);
else
EXPECT_EQ(255u, r);
}
}
}

// test R_FLOAT32 format with min, max, and flip values set
{
// create sample image data for testing
// the image is divided into 4 sections from top to bottom
// The values in the sections are 0.5, 1.0, 1.5, 2.0
auto buffer = std::vector<float>(size);
for (unsigned int i = 0; i < height; ++i)
{
float v = 0.5f * static_cast<int>(i / (height / 4.0) + 1);
for (unsigned int j = 0; j < width; ++j)
{
buffer[i*width + j] = v;
}
}

float min = 0.0f;
float max = 5.0f;
common::Image output;
common::Image::ConvertToRGBImage<float>(
buffer.data(), width, height, output, min, max, true);

// Check RGB data
unsigned char *data = nullptr;
unsigned int outputSize = 0;
output.Data(&data, outputSize);
EXPECT_EQ(size * 3, outputSize);
ASSERT_NE(nullptr, data);

for (unsigned int i = 0u; i < height; ++i)
{
for (unsigned int j = 0u; j < width; ++j)
{
unsigned int r = data[i * width * 3 + j * 3];
unsigned int g = data[i * width * 3 + j * 3 + 1];
unsigned int b = data[i * width * 3 + j * 3 + 2];
EXPECT_EQ(r, g);
EXPECT_EQ(r, b);

// values should be normalized by min, max and flipped
float v = 0.5f * static_cast<int>(i / (height / 4.0) + 1);
unsigned int expectedValue = static_cast<unsigned int>(
(1.0f - ((v - min) / (max - min))) * 255);
EXPECT_EQ(expectedValue, r);
}
}
}
}

using string_int2 = std::tuple<const char *, unsigned int, unsigned int>;

class ImagePerformanceTest : public ImageTest,
Expand Down