diff --git a/src/gfx/CMakeLists.txt b/src/gfx/CMakeLists.txt index 9a787010f..ae621b29f 100644 --- a/src/gfx/CMakeLists.txt +++ b/src/gfx/CMakeLists.txt @@ -2,6 +2,8 @@ # Copyright (C) 2001-2010 David Capello add_library(gfx-lib + hsv.cpp point.cpp rect.cpp + rgb.cpp size.cpp) diff --git a/src/gfx/hsv.cpp b/src/gfx/hsv.cpp new file mode 100644 index 000000000..f107228a9 --- /dev/null +++ b/src/gfx/hsv.cpp @@ -0,0 +1,71 @@ +// ASE gfx library +// Copyright (C) 2001-2010 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#include "gfx/hsv.h" +#include "gfx/rgb.h" +#include + +using namespace gfx; +using namespace std; + +Hsv::Hsv(double hue, double saturation, double value) + : m_hue(hue) + , m_saturation(saturation) + , m_value(value) +{ + while (m_hue < 0.0) + m_hue += 360.0; + m_hue = fmod(hue, 360.0); + + assert(hue >= 0.0 && hue <= 360.0); + assert(saturation >= 0.0 && saturation <= 1.0); + assert(value >= 0.0 && value <= 1.0); +} + +// Reference: http://en.wikipedia.org/wiki/HSL_and_HSV +Hsv::Hsv(const Rgb& rgb) +{ + int M = rgb.maxComponent(); + int m = rgb.minComponent(); + int c = M - m; + double chroma = double(c) / 255.0; + double hue_prime; + double h, s, v; + double r, g, b; + + v = double(M) / 255.0; + + if (c == 0) { + h = 0.0; // Undefined Hue because max == min + s = 0.0; + } + else { + r = double(rgb.red()) / 255.0; + g = double(rgb.green()) / 255.0; + b = double(rgb.blue()) / 255.0; + s = chroma / v; + + if (M == rgb.red()) { + hue_prime = (g - b) / chroma; + + while (hue_prime < 0.0) + hue_prime += 6.0; + hue_prime = fmod(hue_prime, 6.0); + } + else if (M == rgb.green()) { + hue_prime = ((b - r) / chroma) + 2.0; + } + else if (M == rgb.blue()) { + hue_prime = ((r - g) / chroma) + 4.0; + } + + h = hue_prime * 60.0; + } + + m_hue = h; + m_saturation = s; + m_value = v; +} diff --git a/src/gfx/hsv.h b/src/gfx/hsv.h new file mode 100644 index 000000000..4d80c3baf --- /dev/null +++ b/src/gfx/hsv.h @@ -0,0 +1,87 @@ +// ASE gfx library +// Copyright (C) 2001-2010 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#ifndef GFX_HSV_H_INCLUDED +#define GFX_HSV_H_INCLUDED + +#include + +namespace gfx { + +class Rgb; + +class Hsv +{ +public: + Hsv() + : m_hue(0.0) + , m_saturation(0.0) + , m_value(0.0) + { } + + Hsv(double hue, double saturation, double value); + + Hsv(const Hsv& hsv) + : m_hue(hsv.hue()) + , m_saturation(hsv.saturation()) + , m_value(hsv.value()) + { } + + // RGB to HSV conversion + explicit Hsv(const Rgb& rgb); + + double hue() const { + return m_hue; + } + + double saturation() const { + return m_saturation; + } + + double value() const { + return m_value; + } + + // Getters in "int" type, hue=[0,360), saturation=[0,100], value=[0,100] + int hueInt() const { return int(m_hue+0.5); } + int saturationInt() const { return int(m_saturation*100.0+0.5); } + int valueInt() const { return int(m_value*100.0+0.5); } + + void hue(double hue) { + assert(hue >= 0.0 && hue <= 360.0); + m_hue = hue; + } + + void saturation(double saturation) { + assert(saturation >= 0.0 && saturation <= 1.0); + m_saturation = saturation; + } + + void value(double value) { + assert(value >= 0.0 && value <= 1.0); + m_value = value; + } + + bool operator==(const Hsv& other) const { + // Compare floating points as integers (saturation and value are compared in [0,255] range) + return (int(m_hue+0.5) == int(other.m_hue+0.5) && + int(m_saturation*255.0+0.5) == int(other.m_saturation*255.0+0.5) && + int(m_value*255.0+0.5) == int(other.m_value*255.0+0.5)); + } + + bool operator!=(const Hsv& other) const { + return !operator==(other); + } + +private: + double m_hue; + double m_saturation; + double m_value; +}; + +} // namespace gfx + +#endif diff --git a/src/gfx/hsv_unittest.cpp b/src/gfx/hsv_unittest.cpp new file mode 100644 index 000000000..ddd829855 --- /dev/null +++ b/src/gfx/hsv_unittest.cpp @@ -0,0 +1,42 @@ +// ASE gfx library +// Copyright (C) 2001-2010 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#include "tests/test.h" + +#include "gfx/hsv.h" +#include "gfx/rgb.h" + +using namespace gfx; +using namespace std; + +ostream& operator<<(ostream& os, const Hsv& hsv) +{ + return os << "(" + << hsv.hueInt() << ", " + << hsv.saturationInt() << ", " + << hsv.valueInt() << "); real: (" + << hsv.hue() << ", " + << hsv.saturation() << ", " + << hsv.value() << ")"; +} + +TEST(Hsv, FromRgb) +{ + EXPECT_EQ(Hsv( 0.0, 0.000, 0.000), Hsv(Rgb( 0, 0, 0))); + EXPECT_EQ(Hsv( 0.0, 1.000, 0.010), Hsv(Rgb( 3, 0, 0))); + EXPECT_EQ(Hsv( 0.0, 1.000, 0.990), Hsv(Rgb(252, 0, 0))); + EXPECT_EQ(Hsv( 0.0, 1.000, 1.000), Hsv(Rgb(255, 0, 0))); + EXPECT_EQ(Hsv( 60.0, 1.000, 0.750), Hsv(Rgb(191, 191, 0))); + EXPECT_EQ(Hsv(120.0, 1.000, 0.500), Hsv(Rgb( 0, 128, 0))); + EXPECT_EQ(Hsv(120.0, 1.000, 1.000), Hsv(Rgb( 0, 255, 0))); + EXPECT_EQ(Hsv(180.0, 0.500, 1.000), Hsv(Rgb(128, 255, 255))); + EXPECT_EQ(Hsv(240.0, 0.500, 1.000), Hsv(Rgb(128, 128, 255))); + EXPECT_EQ(Hsv(240.0, 1.000, 1.000), Hsv(Rgb( 0, 0, 255))); + EXPECT_EQ(Hsv(300.0, 0.667, 0.750), Hsv(Rgb(191, 64, 191))); + EXPECT_EQ(Hsv(360.0, 1.000, 0.990), Hsv(Rgb(252, 0, 0))); + EXPECT_EQ(Hsv(360.0, 1.000, 1.000), Hsv(Rgb(255, 0, 0))); +} + diff --git a/src/gfx/rgb.cpp b/src/gfx/rgb.cpp new file mode 100644 index 000000000..1a6d83e74 --- /dev/null +++ b/src/gfx/rgb.cpp @@ -0,0 +1,79 @@ +// ASE gfx library +// Copyright (C) 2001-2010 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#include "gfx/rgb.h" +#include "gfx/hsv.h" +#include + +using namespace gfx; +using namespace std; + +// Reference: http://en.wikipedia.org/wiki/HSL_and_HSV +Rgb::Rgb(const Hsv& hsv) +{ + double chroma = hsv.value() * hsv.saturation(); + double hue_prime = hsv.hue() / 60.0; + double x = chroma * (1.0 - fabs(fmod(hue_prime, 2.0) - 1.0)); + double r, g, b; + + r = g = b = 0.0; + + switch (int(hue_prime)) { + + case 6: + case 0: + r = chroma; + g = x; + break; + case 1: + r = x; + g = chroma; + break; + + case 2: + g = chroma; + b = x; + break; + case 3: + g = x; + b = chroma; + break; + + case 4: + b = chroma; + r = x; + break; + case 5: + b = x; + r = chroma; + break; + } + + double m = hsv.value() - chroma; + r += m; + g += m; + b += m; + + m_red = int(r*255.0+0.5); + m_green = int(g*255.0+0.5); + m_blue = int(b*255.0+0.5); +} + +int Rgb::maxComponent() const +{ + if (m_red > m_green) + return (m_red > m_blue) ? m_red: m_blue; + else + return (m_green > m_blue) ? m_green: m_blue; +} + +int Rgb::minComponent() const +{ + if (m_red < m_green) + return (m_red < m_blue) ? m_red: m_blue; + else + return (m_green < m_blue) ? m_green: m_blue; +} diff --git a/src/gfx/rgb.h b/src/gfx/rgb.h new file mode 100644 index 000000000..9f156d557 --- /dev/null +++ b/src/gfx/rgb.h @@ -0,0 +1,92 @@ +// ASE gfx library +// Copyright (C) 2001-2010 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#ifndef GFX_RGB_H_INCLUDED +#define GFX_RGB_H_INCLUDED + +#include + +namespace gfx { + +class Hsv; + +class Rgb +{ +public: + Rgb() + : m_red(0) + , m_green(0) + , m_blue(0) + { } + + Rgb(int red, int green, int blue) + : m_red(red) + , m_green(green) + , m_blue(blue) + { + assert(red >= 0 && red <= 255); + assert(green >= 0 && green <= 255); + assert(blue >= 0 && blue <= 255); + } + + Rgb(const Rgb& rgb) + : m_red(rgb.red()) + , m_green(rgb.green()) + , m_blue(rgb.blue()) + { } + + // HSV to RGB conversion + explicit Rgb(const Hsv& hsv); + + int red() const { + return m_red; + } + + int green() const { + return m_green; + } + + int blue() const { + return m_blue; + } + + int maxComponent() const; + int minComponent() const; + + void red(int red) { + assert(red >= 0 && red <= 255); + m_red = red; + } + + void green(int green) { + assert(green >= 0 && green <= 255); + m_green = green; + } + + void blue(int blue) { + assert(blue >= 0 && blue <= 255); + m_blue = blue; + } + + bool operator==(const Rgb& other) const { + return (m_red == other.m_red && + m_green == other.m_green && + m_blue == other.m_blue); + } + + bool operator!=(const Rgb& other) const { + return !operator==(other); + } + +private: + int m_red; + int m_green; + int m_blue; +}; + +} // namespace gfx + +#endif diff --git a/src/gfx/rgb_unittest.cpp b/src/gfx/rgb_unittest.cpp new file mode 100644 index 000000000..a2ac07e45 --- /dev/null +++ b/src/gfx/rgb_unittest.cpp @@ -0,0 +1,81 @@ +// ASE gfx library +// Copyright (C) 2001-2010 David Capello +// +// This source file is ditributed under a BSD-like license, please +// read LICENSE.txt for more information. + +#include "tests/test.h" + +#include "gfx/rgb.h" +#include "gfx/hsv.h" + +using namespace gfx; +using namespace std; + +ostream& operator<<(ostream& os, const Rgb& rgb) +{ + return os << "(" + << rgb.red() << ", " + << rgb.green() << ", " + << rgb.blue() << ")"; +} + +TEST(Rgb, Ctor) +{ + EXPECT_EQ(1, Rgb(1, 2, 3).red()); + EXPECT_EQ(2, Rgb(1, 2, 3).green()); + EXPECT_EQ(3, Rgb(1, 2, 3).blue()); + EXPECT_EQ(Rgb(0, 0, 0), Rgb()); +} + +TEST(Rgb, Equal) +{ + EXPECT_TRUE(Rgb(1, 2, 3) == Rgb(1, 2, 3)); + EXPECT_FALSE(Rgb(1, 2, 3) != Rgb(1, 2, 3)); + EXPECT_FALSE(Rgb(1, 2, 3) == Rgb(1, 3, 2)); + EXPECT_FALSE(Rgb(0, 0, 0) == Rgb(0, 0, 1)); + EXPECT_FALSE(Rgb(0, 0, 0) == Rgb(0, 1, 0)); + EXPECT_FALSE(Rgb(0, 0, 0) == Rgb(1, 0, 0)); +} + +TEST(Rgb, MaxComponent) +{ + EXPECT_EQ(3, Rgb(1, 2, 3).maxComponent()); + EXPECT_EQ(3, Rgb(1, 3, 2).maxComponent()); + EXPECT_EQ(3, Rgb(2, 1, 3).maxComponent()); + EXPECT_EQ(3, Rgb(2, 3, 1).maxComponent()); + EXPECT_EQ(3, Rgb(3, 1, 2).maxComponent()); + EXPECT_EQ(3, Rgb(3, 2, 1).maxComponent()); +} + +TEST(Rgb, MinComponent) +{ + EXPECT_EQ(1, Rgb(1, 2, 3).minComponent()); + EXPECT_EQ(1, Rgb(1, 3, 2).minComponent()); + EXPECT_EQ(1, Rgb(2, 1, 3).minComponent()); + EXPECT_EQ(1, Rgb(2, 3, 1).minComponent()); + EXPECT_EQ(1, Rgb(3, 1, 2).minComponent()); + EXPECT_EQ(1, Rgb(3, 2, 1).minComponent()); +} + +TEST(Rgb, FromHsv) +{ + for (double hue = 0.0; hue <= 360.0; hue += 10.0) { + EXPECT_EQ(Rgb(255, 255, 255), Rgb(Hsv(hue, 0.000, 1.000))); + EXPECT_EQ(Rgb(128, 128, 128), Rgb(Hsv(hue, 0.000, 0.500))); + EXPECT_EQ(Rgb( 0, 0, 0), Rgb(Hsv(hue, 0.000, 0.000))); + } + + EXPECT_EQ(Rgb( 3, 0, 0), Rgb(Hsv( 0.0, 1.000, 0.010))); + EXPECT_EQ(Rgb(252, 0, 0), Rgb(Hsv( 0.0, 1.000, 0.990))); + EXPECT_EQ(Rgb(255, 0, 0), Rgb(Hsv( 0.0, 1.000, 1.000))); + EXPECT_EQ(Rgb(191, 191, 0), Rgb(Hsv( 60.0, 1.000, 0.750))); + EXPECT_EQ(Rgb( 0, 128, 0), Rgb(Hsv(120.0, 1.000, 0.500))); + EXPECT_EQ(Rgb( 0, 255, 0), Rgb(Hsv(120.0, 1.000, 1.000))); + EXPECT_EQ(Rgb(128, 255, 255), Rgb(Hsv(180.0, 0.500, 1.000))); + EXPECT_EQ(Rgb(128, 128, 255), Rgb(Hsv(240.0, 0.500, 1.000))); + EXPECT_EQ(Rgb( 0, 0, 255), Rgb(Hsv(240.0, 1.000, 1.000))); + EXPECT_EQ(Rgb(191, 64, 191), Rgb(Hsv(300.0, 0.667, 0.750))); + EXPECT_EQ(Rgb(252, 0, 0), Rgb(Hsv(360.0, 1.000, 0.990))); + EXPECT_EQ(Rgb(255, 0, 0), Rgb(Hsv(360.0, 1.000, 1.000))); +}