diff --git a/graphics/include/gz/common/Image.hh b/graphics/include/gz/common/Image.hh index 69d8019a..7f568edd 100644 --- a/graphics/include/gz/common/Image.hh +++ b/graphics/include/gz/common/Image.hh @@ -43,6 +43,7 @@ namespace gz "RGBA_INT8", "BGRA_INT8", "RGB_INT16", + "RGBA_INT16", "RGB_INT32", "BGR_INT8", "BGR_INT16", @@ -72,6 +73,7 @@ namespace gz RGBA_INT8, BGRA_INT8, RGB_INT16, + RGBA_INT16, RGB_INT32, BGR_INT8, BGR_INT16, diff --git a/graphics/src/Image.cc b/graphics/src/Image.cc index 03aadd9d..1fb97de7 100644 --- a/graphics/src/Image.cc +++ b/graphics/src/Image.cc @@ -19,6 +19,7 @@ #endif #include +#include #include #include @@ -37,11 +38,17 @@ namespace gz class Image::Implementation { /// \brief bitmap data - public: FIBITMAP *bitmap; + public: FIBITMAP *bitmap{nullptr}; /// \brief path name of the image file public: std::string fullName; + /// \brief Color type for this image + public: FREE_IMAGE_COLOR_TYPE colorType{FIC_RGB}; + + /// \brief Image type, i.e. pixel format + public: FREE_IMAGE_TYPE imageType{FIT_UNKNOWN}; + /// \brief Implementation of Data, returns vector of bytes public: std::vector DataImpl(FIBITMAP *_img) const; @@ -150,6 +157,9 @@ int Image::Load(const std::string &_filename) return -1; } + this->dataPtr->colorType = FreeImage_GetColorType(this->dataPtr->bitmap); + this->dataPtr->imageType = FreeImage_GetImageType(this->dataPtr->bitmap); + return 0; } @@ -257,6 +267,8 @@ void Image::SetFromData(const unsigned char *_data, this->Height()); FreeImage_Unload(toDelete); } + this->dataPtr->colorType = FreeImage_GetColorType(this->dataPtr->bitmap); + this->dataPtr->imageType = FreeImage_GetImageType(this->dataPtr->bitmap); } ////////////////////////////////////////////////// @@ -285,6 +297,8 @@ void Image::SetFromCompressedData(unsigned char *_data, FIMEMORY *fiMem = FreeImage_OpenMemory(_data, _size); this->dataPtr->bitmap = FreeImage_LoadFromMemory(format, fiMem); FreeImage_CloseMemory(fiMem); + this->dataPtr->colorType = FreeImage_GetColorType(this->dataPtr->bitmap); + this->dataPtr->imageType = FreeImage_GetImageType(this->dataPtr->bitmap); } else { @@ -415,16 +429,22 @@ math::Color Image::Pixel(unsigned int _x, unsigned int _y) const if (!this->Valid()) return clr; - FREE_IMAGE_COLOR_TYPE type = FreeImage_GetColorType(this->dataPtr->bitmap); + if (_x >= this->Width() || _y >= this->Height()) + { + gzerr << "Image: Coordinates out of range[" + << _x << ", " << _y << "] \n"; + return clr; + } - if (type == FIC_RGB || type == FIC_RGBALPHA) + if ((this->dataPtr->colorType == FIC_RGB || + this->dataPtr->colorType == FIC_RGBALPHA) && + (this->dataPtr->imageType == FIT_BITMAP)) { RGBQUAD firgb; - if (FreeImage_GetPixelColor(this->dataPtr->bitmap, _x, _y, &firgb) == FALSE) { - gzerr << "Image: Coordinates out of range[" - << _x << " " << _y << "] \n"; + gzerr << "Failed to get pixel value at [" + << _x << ", " << _y << "] \n"; return clr; } clr.Set(firgb.rgbRed / 255.0f, firgb.rgbGreen / 255.0f, @@ -435,8 +455,8 @@ math::Color Image::Pixel(unsigned int _x, unsigned int _y) const if (this->dataPtr->PixelIndex( this->dataPtr->bitmap, _x, _y, clr) == FALSE) { - gzerr << "Image: Coordinates out of range [" - << _x << " " << _y << "] \n"; + gzerr << "Failed to get pixel value at [" + << _x << ", " << _y << "] \n"; return clr; } } @@ -473,64 +493,20 @@ math::Color Image::AvgColor() const ////////////////////////////////////////////////// math::Color Image::MaxColor() const { - unsigned int x, y; - math::Color clr; math::Color maxClr; - maxClr.Set(0, 0, 0, 0); - if (!this->Valid()) - return clr; - - FREE_IMAGE_COLOR_TYPE type = FreeImage_GetColorType(this->dataPtr->bitmap); - - if (type == FIC_RGB || type == FIC_RGBALPHA) - { - RGBQUAD firgb; + return maxClr; - for (y = 0; y < this->Height(); y++) - { - for (x = 0; x < this->Width(); x++) - { - clr.Set(0, 0, 0, 0); - - if (FALSE == - FreeImage_GetPixelColor(this->dataPtr->bitmap, x, y, &firgb)) - { - gzerr << "Image: Coordinates out of range[" - << x << " " << y << "] \n"; - continue; - } - clr.Set(firgb.rgbRed / 255.0f, firgb.rgbGreen / 255.0f, - firgb.rgbBlue / 255.0f); - - if (clr.R() + clr.G() + clr.B() > maxClr.R() + maxClr.G() + maxClr.B()) - { - maxClr = clr; - } - } - } - } - else + maxClr.Set(0, 0, 0, 0); + for (unsigned int y = 0; y < this->Height(); y++) { - for (y = 0; y < this->Height(); y++) + for (unsigned int x = 0; x < this->Width(); x++) { - for (x = 0; x < this->Width(); x++) + math::Color clr = this->Pixel(x, y); + if (clr.R() + clr.G() + clr.B() > maxClr.R() + maxClr.G() + maxClr.B()) { - clr.Set(0, 0, 0, 0); - - if (this->dataPtr->PixelIndex( - this->dataPtr->bitmap, x, y, clr) == FALSE) - { - gzerr << "Image: Coordinates out of range [" - << x << " " << y << "] \n"; - continue; - } - - if (clr.R() + clr.G() + clr.B() > maxClr.R() + maxClr.G() + maxClr.B()) - { - maxClr = clr; - } + maxClr = clr; } } } @@ -545,9 +521,11 @@ BOOL Image::Implementation::PixelIndex( if (!_dib) return FALSE; - FREE_IMAGE_TYPE imageType = FreeImage_GetImageType(_dib); + if (_x >= FreeImage_GetWidth(_dib) || _y >= FreeImage_GetHeight(_dib)) + return FALSE; + // 8 bit images - if (imageType == FIT_BITMAP) + if (this->imageType == FIT_BITMAP) { BYTE byteValue; // FreeImage_GetPixelIndex should also work with 1 and 4 bit images @@ -563,21 +541,51 @@ BOOL Image::Implementation::PixelIndex( _color.Set(value, value, value); } // 16 bit images - else if (imageType == FIT_UINT16) + else if (this->imageType == FIT_UINT16) { - if ((_x < FreeImage_GetWidth(_dib)) && (_y < FreeImage_GetHeight(_dib))) - { - WORD *bits = reinterpret_cast(FreeImage_GetScanLine(_dib, _y)); - uint16_t word = static_cast(bits[_x]); - // convert to float value between 0-1 - float value = word / static_cast(math::MAX_UI16); - _color.Set(value, value, value); - } - else - { - return FALSE; - } + WORD *bits = reinterpret_cast(FreeImage_GetScanLine(_dib, _y)); + uint16_t word = static_cast(bits[_x]); + // convert to float value between 0-1 + float value = word / static_cast(math::MAX_UI16); + _color.Set(value, value, value); } + else if (this->imageType == FIT_INT16) + { + WORD *bits = reinterpret_cast(FreeImage_GetScanLine(_dib, _y)); + int16_t word = static_cast(bits[_x]); + // convert to float value between 0-1 + float value = word / static_cast(math::MAX_I16); + _color.Set(value, value, value); + } + else if (this->imageType == FIT_RGB16) + { + const unsigned int channels = 3u; + WORD *bits = reinterpret_cast(FreeImage_GetScanLine(_dib, _y)); + uint16_t r = static_cast(bits[_x * channels]); + uint16_t g = static_cast(bits[_x * channels + 1]); + uint16_t b = static_cast(bits[_x * channels + 2]); + // convert to float value between 0-1 + float valueR = r / static_cast(math::MAX_UI16); + float valueG = g / static_cast(math::MAX_UI16); + float valueB = b / static_cast(math::MAX_UI16); + _color.Set(valueR, valueG, valueB); + } + else if (this->imageType == FIT_RGBA16) + { + const unsigned int channels = 4u; + WORD *bits = reinterpret_cast(FreeImage_GetScanLine(_dib, _y)); + uint16_t r = static_cast(bits[_x * channels]); + uint16_t g = static_cast(bits[_x * channels + 1]); + uint16_t b = static_cast(bits[_x * channels + 2]); + uint16_t a = static_cast(bits[_x * channels + 3]); + // convert to float value between 0-1 + float valueR = r / static_cast(math::MAX_UI16); + float valueG = g / static_cast(math::MAX_UI16); + float valueB = b / static_cast(math::MAX_UI16); + float valueA = a / static_cast(math::MAX_UI16); + _color.Set(valueR, valueG, valueB, valueA); + } + return TRUE; } @@ -634,6 +642,8 @@ Image::PixelFormatType Image::PixelFormat() const } else if (type == FIT_RGB16) fmt = RGB_INT16; + else if (type == FIT_RGBA16) + fmt = RGBA_INT16; else if (type == FIT_RGBF) fmt = RGB_FLOAT32; else if (type == FIT_UINT16 || type == FIT_INT16) diff --git a/graphics/src/Image_TEST.cc b/graphics/src/Image_TEST.cc index 645f2592..787a5631 100644 --- a/graphics/src/Image_TEST.cc +++ b/graphics/src/Image_TEST.cc @@ -15,6 +15,7 @@ * */ #include +#include #include @@ -413,6 +414,8 @@ TEST_F(ImageTest, ConvertPixelFormat) Image::ConvertPixelFormat("RGBA_INT8")); EXPECT_EQ(Image::PixelFormatType::RGB_INT16, Image::ConvertPixelFormat("RGB_INT16")); + EXPECT_EQ(Image::PixelFormatType::RGBA_INT16, + Image::ConvertPixelFormat("RGBA_INT16")); EXPECT_EQ(Image::PixelFormatType::RGB_INT32, Image::ConvertPixelFormat("RGB_INT32")); EXPECT_EQ(Image::PixelFormatType::BGR_INT8, @@ -673,6 +676,62 @@ TEST_F(ImageTest, Grayscale) } } + +///////////////////////////////////////////////// +TEST_F(ImageTest, Color16bit) +{ + { + common::Image img; + std::string fileName = common::testing::TestFile("data", + "rgb_16bit.png"); + EXPECT_EQ(0, img.Load(fileName)); + const unsigned int width = 4u; + const unsigned int height = 4u; + const unsigned int channels = 3u; + const unsigned int bpp = channels * 16u; + EXPECT_TRUE(img.Valid()); + EXPECT_EQ(width, img.Width()); + EXPECT_EQ(height, img.Height()); + EXPECT_EQ(bpp, img.BPP()); + EXPECT_EQ(width * bpp / 8u, img.Pitch()); + EXPECT_EQ(common::Image::PixelFormatType::RGB_INT16, img.PixelFormat()); + math::Color maxColor(0.0f, 0.0f, 0.847f); + EXPECT_NEAR(maxColor.R(), img.MaxColor().R(), 1e-3); + EXPECT_NEAR(maxColor.G(), img.MaxColor().G(), 1e-3); + EXPECT_NEAR(maxColor.B(), img.MaxColor().B(), 1e-3); + math::Color pixelWithMaxColor = img.Pixel(3, 1); + EXPECT_NEAR(maxColor.R(), pixelWithMaxColor.R(), 1e-3); + EXPECT_NEAR(maxColor.G(), pixelWithMaxColor.G(), 1e-3); + EXPECT_NEAR(maxColor.B(), pixelWithMaxColor.B(), 1e-3); + } + { + common::Image img; + std::string fileName = common::testing::TestFile("data", + "rgba_16bit.png"); + EXPECT_EQ(0, img.Load(fileName)); + const unsigned int width = 4u; + const unsigned int height = 4u; + const unsigned int channels = 4u; + const unsigned int bpp = channels * 16u; + EXPECT_TRUE(img.Valid()); + EXPECT_EQ(width, img.Width()); + EXPECT_EQ(height, img.Height()); + EXPECT_EQ(bpp, img.BPP()); + EXPECT_EQ(width * bpp / 8u, img.Pitch()); + EXPECT_EQ(common::Image::PixelFormatType::RGBA_INT16, img.PixelFormat()); + math::Color maxColor(0.0f, 0.0f, 0.847f, 0.5f); + EXPECT_NEAR(maxColor.R(), img.MaxColor().R(), 1e-3); + EXPECT_NEAR(maxColor.G(), img.MaxColor().G(), 1e-3); + EXPECT_NEAR(maxColor.B(), img.MaxColor().B(), 1e-3); + EXPECT_NEAR(maxColor.A(), img.MaxColor().A(), 1e-3); + math::Color pixelWithMaxColor = img.Pixel(3, 1); + EXPECT_NEAR(maxColor.R(), pixelWithMaxColor.R(), 1e-3); + EXPECT_NEAR(maxColor.G(), pixelWithMaxColor.G(), 1e-3); + EXPECT_NEAR(maxColor.B(), pixelWithMaxColor.B(), 1e-3); + EXPECT_NEAR(maxColor.A(), pixelWithMaxColor.A(), 1e-3); + } +} + using string_int2 = std::tuple; class ImagePerformanceTest : public ImageTest, diff --git a/test/data/rgb_16bit.png b/test/data/rgb_16bit.png new file mode 100644 index 00000000..b0360c6c Binary files /dev/null and b/test/data/rgb_16bit.png differ diff --git a/test/data/rgba_16bit.png b/test/data/rgba_16bit.png new file mode 100644 index 00000000..bc2f55c1 Binary files /dev/null and b/test/data/rgba_16bit.png differ