From adc707adb472dd73f80c96078b9e159e916e333f Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Thu, 22 Apr 2021 19:02:21 -0700 Subject: [PATCH 1/6] add ConvertToRGBImage function and test Signed-off-by: Ian Chen --- graphics/include/ignition/common/Image.hh | 72 ++++++++ graphics/src/Image_TEST.cc | 195 ++++++++++++++++++++++ 2 files changed, 267 insertions(+) diff --git a/graphics/include/ignition/common/Image.hh b/graphics/include/ignition/common/Image.hh index 9be8d9e6a..ed82843e4 100644 --- a/graphics/include/ignition/common/Image.hh +++ b/graphics/include/ignition/common/Image.hh @@ -185,6 +185,78 @@ 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] _min 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 + static void ConvertToRGBImage(const void *_data, + unsigned int _width, unsigned int _height, Image &_output, + T _min = std::numeric_limits::max(), + T _max = std::numeric_limits::lowest(), bool _flip = false) + { + unsigned int samples = _width * _height; + unsigned int bufferSize = samples * sizeof(T); + T *buffer = new T[samples]; + memcpy(buffer, _data, bufferSize); + + unsigned char *outputRgbBuffer = new unsigned char[samples * 3]; + + // use min and max values found in the data if not specified + T min = std::numeric_limits::max(); + T max = std::numeric_limits::lowest(); + if (_min > max) + { + for (unsigned int i = 0; i < samples; ++i) + { + T v = static_cast(buffer[i]); + if (v > max && !std::isinf(v)) + max = v; + if (v < min && !std::isinf(v)) + min = v; + } + } + min = math::equal(_min, std::numeric_limits::max()) ? min : _min; + max = math::equal(_max, std::numeric_limits::lowest()) ? max : _max; + + // convert to rgb image + // color is grayscale, i.e. r == b == g + double range = static_cast(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) + { + T v = static_cast(buffer[idx++]); + double t = static_cast(v-min) / range; + if (_flip) + t = 1.0 - t; + uint8_t r = static_cast(255*t); + unsigned int outIdx = j * _width * 3 + i * 3; + outputRgbBuffer[outIdx] = r; + outputRgbBuffer[outIdx + 1] = r; + outputRgbBuffer[outIdx + 2] = r; + } + } + _output.SetFromData(outputRgbBuffer, _width, _height, RGB_INT8); + delete [] outputRgbBuffer; + delete [] buffer; + } + IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING /// \brief Private data pointer private: std::unique_ptr dataPtr; diff --git a/graphics/src/Image_TEST.cc b/graphics/src/Image_TEST.cc index e9f41df93..45395dfea 100644 --- a/graphics/src/Image_TEST.cc +++ b/graphics/src/Image_TEST.cc @@ -325,6 +325,201 @@ 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 + uint8_t *buffer = new uint8_t[size]; + for (unsigned int i = 0; i < height; ++i) + { + uint8_t v = 10 * static_cast(i / (width/ 4.0) + 1) ; + for (unsigned int j = 0; j < width; ++j) + { + buffer[i*width + j] = v; + } + } + + common::Image output; + common::Image::ConvertToRGBImage(buffer, 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(255 / 3), r); + else if (i >= (height / 2.0) && i < (height / 4.0 * 3.0)) + EXPECT_EQ(static_cast(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 + uint16_t *buffer = new uint16_t[size]; + for (unsigned int i = 0; i < height; ++i) + { + uint16_t v = 100 * static_cast(i / (height / 4.0) + 1) ; + for (unsigned int j = 0; j < width; ++j) + { + buffer[i*width + j] = v; + } + } + + common::Image output; + common::Image::ConvertToRGBImage(buffer, 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(255 / 3), r); + else if (i >= (height / 2.0) && i < (height / 4.0 * 3.0)) + EXPECT_EQ(static_cast(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 + float *buffer = new float[size]; + for (unsigned int i = 0; i < height; ++i) + { + float v = 0.5f * static_cast(i / (height / 4.0) + 1) ; + for (unsigned int j = 0; j < width; ++j) + { + buffer[i*width + j] = v; + } + } + + common::Image output; + common::Image::ConvertToRGBImage(buffer, 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(255 / 3), r); + else if (i >= (height / 2.0) && i < (height / 4.0 * 3.0)) + EXPECT_EQ(static_cast(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 + float *buffer = new float[size]; + for (unsigned int i = 0; i < height; ++i) + { + float v = 0.5f * static_cast(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(buffer, 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(i / (height / 4.0) + 1); + unsigned int expectedValue = static_cast( + (1.0f - ((v - min) / (max - min))) * 255); + EXPECT_EQ(expectedValue, r); + } + } + } +} + using string_int2 = std::tuple; class ImagePerformanceTest : public ImageTest, From 010617ab8bc26ccc022162361c74114a758dde27 Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Fri, 23 Apr 2021 13:01:19 -0700 Subject: [PATCH 2/6] codecheck Signed-off-by: Ian Chen --- graphics/src/Image_TEST.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graphics/src/Image_TEST.cc b/graphics/src/Image_TEST.cc index 45395dfea..884cbe7ac 100644 --- a/graphics/src/Image_TEST.cc +++ b/graphics/src/Image_TEST.cc @@ -340,7 +340,7 @@ TEST_F(ImageTest, ConvertToRGBImage) uint8_t *buffer = new uint8_t[size]; for (unsigned int i = 0; i < height; ++i) { - uint8_t v = 10 * static_cast(i / (width/ 4.0) + 1) ; + uint8_t v = 10 * static_cast(i / (width/ 4.0) + 1); for (unsigned int j = 0; j < width; ++j) { buffer[i*width + j] = v; @@ -386,7 +386,7 @@ TEST_F(ImageTest, ConvertToRGBImage) uint16_t *buffer = new uint16_t[size]; for (unsigned int i = 0; i < height; ++i) { - uint16_t v = 100 * static_cast(i / (height / 4.0) + 1) ; + uint16_t v = 100 * static_cast(i / (height / 4.0) + 1); for (unsigned int j = 0; j < width; ++j) { buffer[i*width + j] = v; @@ -433,7 +433,7 @@ TEST_F(ImageTest, ConvertToRGBImage) float *buffer = new float[size]; for (unsigned int i = 0; i < height; ++i) { - float v = 0.5f * static_cast(i / (height / 4.0) + 1) ; + float v = 0.5f * static_cast(i / (height / 4.0) + 1); for (unsigned int j = 0; j < width; ++j) { buffer[i*width + j] = v; @@ -480,7 +480,7 @@ TEST_F(ImageTest, ConvertToRGBImage) float *buffer = new float[size]; for (unsigned int i = 0; i < height; ++i) { - float v = 0.5f * static_cast(i / (height / 4.0) + 1) ; + float v = 0.5f * static_cast(i / (height / 4.0) + 1); for (unsigned int j = 0; j < width; ++j) { buffer[i*width + j] = v; From f3eb424a21d48e4cfa0e3e7714daeb8c3c808c83 Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Fri, 23 Apr 2021 13:02:02 -0700 Subject: [PATCH 3/6] one more Signed-off-by: Ian Chen --- graphics/include/ignition/common/Image.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/graphics/include/ignition/common/Image.hh b/graphics/include/ignition/common/Image.hh index ed82843e4..ff8457d44 100644 --- a/graphics/include/ignition/common/Image.hh +++ b/graphics/include/ignition/common/Image.hh @@ -17,6 +17,7 @@ #ifndef IGNITION_COMMON_IMAGE_HH_ #define IGNITION_COMMON_IMAGE_HH_ +#include #include #include #include From e1a53c140986201028fc2abbcadb6edf92fcc47d Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Fri, 23 Apr 2021 17:37:33 -0700 Subject: [PATCH 4/6] fixing windows Signed-off-by: Ian Chen --- graphics/include/ignition/common/Image.hh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/graphics/include/ignition/common/Image.hh b/graphics/include/ignition/common/Image.hh index ff8457d44..432c4adee 100644 --- a/graphics/include/ignition/common/Image.hh +++ b/graphics/include/ignition/common/Image.hh @@ -223,9 +223,12 @@ namespace ignition for (unsigned int i = 0; i < samples; ++i) { T v = static_cast(buffer[i]); - if (v > max && !std::isinf(v)) + // 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(v))) max = v; - if (v < min && !std::isinf(v)) + if (v < min && !std::isinf(static_cast(v))) min = v; } } From fe9fffe6e07e66f4b28d96632ed02571e8591ddc Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Mon, 26 Apr 2021 11:03:37 -0700 Subject: [PATCH 5/6] typos Signed-off-by: Ian Chen --- graphics/include/ignition/common/Image.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/include/ignition/common/Image.hh b/graphics/include/ignition/common/Image.hh index 432c4adee..683652014 100644 --- a/graphics/include/ignition/common/Image.hh +++ b/graphics/include/ignition/common/Image.hh @@ -198,9 +198,9 @@ namespace ignition /// \param[out] _output Output RGB image /// \param[in] _min Minimum value to be used when normalizing the input /// image data to RGB. - /// \param[in] _min Maximum value to be used when normalizing the input + /// \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. + /// \param[in] _flip True to flip the values after normalization, i.e. /// lower values are converted to brigher pixels. public: template static void ConvertToRGBImage(const void *_data, From 90eba51d5fb8d94c4afa878547cda14d45020cc1 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Tue, 27 Apr 2021 07:24:42 -0500 Subject: [PATCH 6/6] Remove raw pointers (#206) Signed-off-by: Michael Carroll --- graphics/include/ignition/common/Image.hh | 17 ++++++++--------- graphics/src/Image_TEST.cc | 21 ++++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/graphics/include/ignition/common/Image.hh b/graphics/include/ignition/common/Image.hh index 683652014..b78d01b51 100644 --- a/graphics/include/ignition/common/Image.hh +++ b/graphics/include/ignition/common/Image.hh @@ -210,10 +210,11 @@ namespace ignition { unsigned int samples = _width * _height; unsigned int bufferSize = samples * sizeof(T); - T *buffer = new T[samples]; - memcpy(buffer, _data, bufferSize); - unsigned char *outputRgbBuffer = new unsigned char[samples * 3]; + auto buffer = std::vector(samples); + memcpy(buffer.data(), _data, bufferSize); + + auto outputRgbBuffer = std::vector(samples * 3); // use min and max values found in the data if not specified T min = std::numeric_limits::max(); @@ -222,7 +223,7 @@ namespace ignition { for (unsigned int i = 0; i < samples; ++i) { - T v = static_cast(buffer[i]); + auto v = buffer[i]; // ignore inf values when computing min/max // cast to float when calling isinf to avoid compile error on // windows @@ -245,8 +246,8 @@ namespace ignition { for (unsigned int i = 0; i < _width; ++i) { - T v = static_cast(buffer[idx++]); - double t = static_cast(v-min) / range; + auto v = buffer[idx++]; + double t = static_cast(v - min) / range; if (_flip) t = 1.0 - t; uint8_t r = static_cast(255*t); @@ -256,9 +257,7 @@ namespace ignition outputRgbBuffer[outIdx + 2] = r; } } - _output.SetFromData(outputRgbBuffer, _width, _height, RGB_INT8); - delete [] outputRgbBuffer; - delete [] buffer; + _output.SetFromData(outputRgbBuffer.data(), _width, _height, RGB_INT8); } IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING diff --git a/graphics/src/Image_TEST.cc b/graphics/src/Image_TEST.cc index 884cbe7ac..d7b4e3c24 100644 --- a/graphics/src/Image_TEST.cc +++ b/graphics/src/Image_TEST.cc @@ -337,7 +337,7 @@ TEST_F(ImageTest, ConvertToRGBImage) // 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 - uint8_t *buffer = new uint8_t[size]; + auto buffer = std::vector(size); for (unsigned int i = 0; i < height; ++i) { uint8_t v = 10 * static_cast(i / (width/ 4.0) + 1); @@ -348,7 +348,8 @@ TEST_F(ImageTest, ConvertToRGBImage) } common::Image output; - common::Image::ConvertToRGBImage(buffer, width, height, output); + common::Image::ConvertToRGBImage( + buffer.data(), width, height, output); // Check RGBA data unsigned char *data = nullptr; @@ -383,7 +384,7 @@ TEST_F(ImageTest, ConvertToRGBImage) // 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 - uint16_t *buffer = new uint16_t[size]; + auto buffer = std::vector(size); for (unsigned int i = 0; i < height; ++i) { uint16_t v = 100 * static_cast(i / (height / 4.0) + 1); @@ -394,7 +395,8 @@ TEST_F(ImageTest, ConvertToRGBImage) } common::Image output; - common::Image::ConvertToRGBImage(buffer, width, height, output); + common::Image::ConvertToRGBImage( + buffer.data(), width, height, output); // Check RGB data unsigned char *data = nullptr; @@ -430,7 +432,7 @@ TEST_F(ImageTest, ConvertToRGBImage) // 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 - float *buffer = new float[size]; + auto buffer = std::vector(size); for (unsigned int i = 0; i < height; ++i) { float v = 0.5f * static_cast(i / (height / 4.0) + 1); @@ -441,7 +443,8 @@ TEST_F(ImageTest, ConvertToRGBImage) } common::Image output; - common::Image::ConvertToRGBImage(buffer, width, height, output); + common::Image::ConvertToRGBImage( + buffer.data(), width, height, output); // Check RGB data unsigned char *data = nullptr; @@ -477,7 +480,7 @@ TEST_F(ImageTest, ConvertToRGBImage) // 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 - float *buffer = new float[size]; + auto buffer = std::vector(size); for (unsigned int i = 0; i < height; ++i) { float v = 0.5f * static_cast(i / (height / 4.0) + 1); @@ -490,8 +493,8 @@ TEST_F(ImageTest, ConvertToRGBImage) float min = 0.0f; float max = 5.0f; common::Image output; - common::Image::ConvertToRGBImage(buffer, width, height, output, - min, max, true); + common::Image::ConvertToRGBImage( + buffer.data(), width, height, output, min, max, true); // Check RGB data unsigned char *data = nullptr;