diff --git a/colour_extractor.cpp b/colour_extractor.cpp new file mode 100644 index 0000000..2e1d588 --- /dev/null +++ b/colour_extractor.cpp @@ -0,0 +1,195 @@ +#include "colour_extractor.h" + +#include +#include + +// CONSTRUCTORS +ColourExtractor::ColourExtractor(size_t h_levels, size_t s_levels, + size_t v_levels) +:m_pixels(NULL), m_max_colour_count(0), m_h_levels(h_levels), + m_s_levels(s_levels), m_v_levels(v_levels) +{ + m_pixels = new size_t[m_h_levels * m_s_levels * m_v_levels]; + memset(m_pixels, 0, m_h_levels * m_s_levels * m_v_levels * sizeof(size_t)); +} + +// PROTECTED FUNCS +size_t ColourExtractor::quantize(struct ColourHSV hsv) const +{ + size_t result = 0; + + size_t h_quant = 0; + size_t s_quant = 0; + size_t v_quant = 0; + + v_quant = static_cast(round(hsv.v * (m_v_levels - 1))); + if (0 == v_quant) + { + goto end; + } + + s_quant = static_cast(round(hsv.s * (m_s_levels - 1))); + if (0 == s_quant) + { + goto end; + } + + h_quant = static_cast(round(hsv.h / 360.0 * (m_h_levels - 1))); + + end: + result += h_quant; + + result *= m_s_levels; + result += s_quant; + + result *= m_v_levels; + result += v_quant; + + return result; +} + +struct ColourHSV ColourExtractor::dequantize(size_t colour) const +{ + struct ColourHSV result; + size_t h_quant; + size_t s_quant; + size_t v_quant; + + v_quant = colour % m_v_levels; + colour /= m_v_levels; + + s_quant = colour % m_s_levels; + colour /= m_s_levels; + + h_quant = colour % m_h_levels; + + result.h = static_cast(h_quant) * 360.0 / (m_h_levels - 1); + result.s = static_cast(s_quant) / (m_s_levels - 1); + result.v = static_cast(v_quant) / (m_v_levels - 1); + + return result; +} + +// PUBLIC FUNCS +void ColourExtractor::get_quantization_levels(size_t& h_levels, + size_t& s_levels, size_t& v_levels) const +{ + h_levels = m_h_levels; + s_levels = m_s_levels; + v_levels = m_v_levels; +} + +void ColourExtractor::set_quantization_levels(size_t h_levels, size_t s_levels, + size_t v_levels) +{ + delete[] m_pixels; + + m_h_levels = h_levels; + m_s_levels = s_levels; + m_v_levels = v_levels; + + m_pixels = new size_t[m_h_levels * m_s_levels * m_v_levels]; + memset(m_pixels, 0, m_h_levels * m_s_levels * m_v_levels * sizeof(size_t)); +} + +size_t ColourExtractor::get_h_quantization_levels() const +{ + return m_h_levels; +} + +void ColourExtractor::set_h_quantization_levels(size_t h_levels) +{ + delete[] m_pixels; + + m_h_levels = h_levels; + + m_pixels = new size_t[m_h_levels * m_s_levels * m_v_levels]; + memset(m_pixels, 0, m_h_levels * m_s_levels * m_v_levels * sizeof(size_t)); +} + +size_t ColourExtractor::get_s_quantization_levels() const +{ + return m_s_levels; +} + +void ColourExtractor::set_s_quantization_levels(size_t s_levels) +{ + delete[] m_pixels; + + m_s_levels = s_levels; + + m_pixels = new size_t[m_h_levels * m_s_levels * m_v_levels]; + memset(m_pixels, 0, m_h_levels * m_s_levels * m_v_levels * sizeof(size_t)); +} + +size_t ColourExtractor::get_v_quantization_levels() const +{ + return m_v_levels; +} + +void ColourExtractor::set_v_quantization_levels(size_t v_levels) +{ + delete[] m_pixels; + + m_v_levels = v_levels; + + m_pixels = new size_t[m_h_levels * m_s_levels * m_v_levels]; + memset(m_pixels, 0, m_h_levels * m_s_levels * m_v_levels * sizeof(size_t)); +} + +void ColourExtractor::add_pixel(struct ColourHSV hsv) +{ + size_t colour_quant; + + colour_quant = quantize(hsv); + m_pixels[colour_quant] += 1; + + if (m_max_colour_count < m_pixels[colour_quant]) + { + m_max_colour_count = m_pixels[colour_quant]; + } +} + +void ColourExtractor::clear_pixels() +{ + for (size_t i = 0; i < m_h_levels * m_s_levels * m_v_levels; ++i) + { + m_pixels[i] = 0; + } + m_max_colour_count = 0; +} + +struct ColourHSV ColourExtractor::extract_colour() const +{ + struct ColourHSV result = {0.0, 0.0, 0.0}; + struct ColourHSV temp_colour; + size_t max_colour_value = 0; + size_t temp_colour_value; + double temp_colour_weight; + + for (size_t i = 0; i < m_h_levels * m_s_levels * m_v_levels; ++i) + { + if (0 == m_pixels[i]) + { + continue; + } + + temp_colour = dequantize(i); + temp_colour_weight = static_cast(m_pixels[i]); + temp_colour_weight /= m_max_colour_count; + + temp_colour_value = evaluate_colour(temp_colour, temp_colour_weight); + if (temp_colour_value > max_colour_value) + { + result = temp_colour; + max_colour_value = temp_colour_value; + } + } + + return result; +} + +ColourExtractor::~ColourExtractor() +{ + delete[] m_pixels; +} diff --git a/colour_extractor.h b/colour_extractor.h new file mode 100644 index 0000000..90ef18e --- /dev/null +++ b/colour_extractor.h @@ -0,0 +1,72 @@ +#ifndef COLOUR_EXTRACTOR_H_ +#define COLOUR_EXTRACTOR_H_ + +#include + +#include "colours.h" + +class ColourExtractor +{ +private: + // Holds all quantized pixels + size_t* m_pixels; + + // Holds the ammount of the colour that appears the most + size_t m_max_colour_count; + + // Quantization levels for the HSV colours + size_t m_h_levels; + size_t m_s_levels; + size_t m_v_levels; + + // Quantizes the colour + size_t quantize(struct ColourHSV hsv) const; + + // Dequantizes the colour + struct ColourHSV dequantize(size_t colour) const; + +protected: + // Evaluates the given colour based on its weight + virtual size_t evaluate_colour(struct ColourHSV hsv, double weight) + const = 0; + +public: + // Params: HSV quantization levels + ColourExtractor(size_t h_levels = 36, size_t s_levels = 10, + size_t v_levels = 10); + + void get_quantization_levels(size_t& h_levels, size_t& s_levels, + size_t& v_levels) const; + + // Note: clears all pixels + void set_quantization_levels(size_t h_levels, size_t s_levels, + size_t v_levels); + + size_t get_h_quantization_levels() const; + + // Note: clears all pixels + void set_h_quantization_levels(size_t h_levels); + + size_t get_s_quantization_levels() const; + + // Note: clears all pixels + void set_s_quantization_levels(size_t s_levels); + + size_t get_v_quantization_levels() const; + + // Note: clears all pixels + void set_v_quantization_levels(size_t v_levels); + + // Adds a pixel to be considered for extraction + void add_pixel(struct ColourHSV hsv); + + // Clears all pixels + void clear_pixels(); + + // Extracts a single colour from all added pixels + struct ColourHSV extract_colour() const; + + ~ColourExtractor(); +}; + +#endif // COLOUR_EXTRACTOR_H_ diff --git a/colours.c b/colours.c new file mode 100644 index 0000000..bead41a --- /dev/null +++ b/colours.c @@ -0,0 +1,188 @@ +#include "colours.h" + +#include + +struct ColourRGB CMYKtoRGB(struct ColourCMYK cmyk) +{ + struct ColourRGB result; + + result.r = (1.0 - cmyk.c) * (1.0 - cmyk.k); + result.g = (1.0 - cmyk.m) * (1.0 - cmyk.k); + result.b = (1.0 - cmyk.y) * (1.0 - cmyk.k); + + return result; +} + +struct ColourRGB HSVtoRGB(struct ColourHSV hsv) +{ + struct ColourRGB result; + double c; + double x; + double m; + + c = hsv.s * hsv.v; + x = c * (1 - fabs(fmod(hsv.h / 60.0, 2) - 1)); + m = hsv.v - c; + + int angle = hsv.h / 60; + switch (angle) + { + // 0 - 60 + case 6: + case 0: + result.r = c; + result.g = x; + result.b = 0; + break; + // 60 - 120 + case 1: + result.r = x; + result.g = c; + result.b = 0; + break; + // 120 - 180 + case 2: + result.r = 0; + result.g = c; + result.b = x; + break; + // 180 - 240 + case 3: + result.r = 0; + result.g = x; + result.b = c; + break; + // 240 - 300 + case 4: + result.r = x; + result.g = 0; + result.b = c; + break; + // 300 - 360 + case 5: + result.r = c; + result.g = 0; + result.b = x; + break; + // Should never happen + default: + result.r = 0; + result.g = 0; + result.b = 0; + break; + } + + result.r += m; + result.g += m; + result.b += m; + + return result; +} + +struct ColourRGB HSLtoRGB(struct ColourHSL hsl) +{ + +} + +struct ColourCMYK RGBtoCMYK(struct ColourRGB rgb) +{ + struct ColourCMYK result; + + + // max(r,g,b) + result.k = rgb.r > rgb.g ? rgb.r : rgb.g; + result.k = rgb.b > result.k ? rgb.b : result.k; + result.k = 1.0 - result.k; + + if (1.0 == result.k) + { + result.c = 0; + result.m = 0; + result.y = 0; + } + else + { + result.c = (1.0 - rgb.r - result.k) / (1.0 - result.k); + result.m = (1.0 - rgb.g - result.k) / (1.0 - result.k); + result.y = (1.0 - rgb.b - result.k) / (1.0 - result.k); + } + + return result; +} + +struct ColourCMYK HSVtoCMYK(struct ColourHSV hsv) +{ + struct ColourCMYK result; + + result = RGBtoCMYK(HSVtoRGB(hsv)); + + return result; +} + +struct ColourHSV RGBtoHSV(struct ColourRGB rgb) +{ + struct ColourHSV result; + + double c_max; + double c_min; + double delta; + + // max(r,g,b) + c_max = rgb.r > rgb.g ? rgb.r : rgb.g; + c_max = rgb.b > c_max ? rgb.b : c_max; + + // min(r,g,b) + c_min = rgb.r < rgb.g ? rgb.r : rgb.g; + c_min = rgb.b < c_min ? rgb.b : c_min; + + delta = c_max - c_min; + + if (delta > 0) + { + if (c_max == rgb.r) + { + result.h = 60 * fmod((rgb.g - rgb.b) / delta, 6); + } + else if (c_max == rgb.g) + { + result.h = 60 * ((rgb.b - rgb.r) / delta + 2); + } + else + { + result.h = 60 * ((rgb.r - rgb.g) / delta + 4); + } + + if(c_max > 0) + { + result.s = delta / c_max; + } + else + { + result.s = 0; + } + + result.v = c_max; + } + else + { + result.h = 0; + result.s = 0; + result.v = c_max; + } + + if (result.h < 0) + { + result.h += 360; + } + + return result; +} + +struct ColourHSV CMYKtoHSV(struct ColourCMYK cmyk) +{ + struct ColourHSV result; + + result = RGBtoHSV(CMYKtoRGB(cmyk)); + + return result; +} diff --git a/colours.h b/colours.h new file mode 100644 index 0000000..7b68a1f --- /dev/null +++ b/colours.h @@ -0,0 +1,49 @@ +#ifndef COLOURS_H_ +#define COLOURS_H_ + +struct ColourRGB +{ + double r; + double g; + double b; +}; + +struct ColourCMYK +{ + double c; + double m; + double y; + double k; +}; + +struct ColourHSV +{ + double h; + double s; + double v; +}; + +struct ColourHSL +{ + double h; + double s; + double l; +}; + +struct ColourRGB CMYKtoRGB(struct ColourCMYK cmyk); +struct ColourRGB HSVtoRGB(struct ColourHSV hsv); +struct ColourRGB HSLtoRGB(struct ColourHSL hsl); + +struct ColourCMYK RGBtoCMYK(struct ColourRGB rgb); +struct ColourCMYK HSVtoCMYK(struct ColourHSV hsv); +struct ColourCMYK HSLtoCMYK(struct ColourHSL hsl); + +struct ColourHSV RGBtoHSV(struct ColourRGB rgb); +struct ColourHSV CMYKtoHSV(struct ColourCMYK cmyk); +struct ColourHSV HSLtoHSV(struct ColourHSL hsl); + +struct ColourHSL RGBtoHSL(struct ColourRGB rgb); +struct ColourHSL CMYKtoHSL(struct ColourCMYK cmyk); +struct ColourHSL HSLtoHSL(struct ColourHSV hsv); + +#endif // COLOURS_H_ diff --git a/complimentary_colour_extractor.cpp b/complimentary_colour_extractor.cpp new file mode 100644 index 0000000..8668ff0 --- /dev/null +++ b/complimentary_colour_extractor.cpp @@ -0,0 +1,55 @@ +#include "complimentary_colour_extractor.h" + +#include + +// CONSTRUCTORS +ComplimentaryColourExtractor::ComplimentaryColourExtractor(struct ColourHSV hsv, + size_t h_levels, size_t s_levels, size_t v_levels) +:ColourExtractor(h_levels, s_levels, v_levels), m_main_colour(hsv) +{ +} + +// PROTECTED FUNCS +size_t ComplimentaryColourExtractor::evaluate_colour(struct ColourHSV hsv, + double weight) const +{ + size_t result = 0; + double angle_delta; + double additional_colour_weight; + + if ((0.0 == hsv.s) || (0.0 == m_main_colour.s)) + { + angle_delta = 180.0; + additional_colour_weight = 0.5; + } + else + { + additional_colour_weight = 1.0; + /* SECTION BEGIN */ + angle_delta = fabs(hsv.h - m_main_colour.h); + if (angle_delta > 180.0F) + { + angle_delta = 360.0F - angle_delta; + } + /* SECTION END */ + } + + // result = static_cast(round(1000.0 * pow(weight, 0.3) * hsv.v * + // fmin(1.0, hsv.v + hsv.s) * additional_colour_weight * angle_delta)); + + result = static_cast(round(1000.0 * pow(weight, 0.3) * hsv.v * + additional_colour_weight * angle_delta)); + + return result; +} + +// PUBLIC FUNCS +struct ColourHSV ComplimentaryColourExtractor::get_main_colour() const +{ + return m_main_colour; +} + +void ComplimentaryColourExtractor::set_main_colour(struct ColourHSV hsv) +{ + m_main_colour = hsv; +} diff --git a/complimentary_colour_extractor.h b/complimentary_colour_extractor.h new file mode 100644 index 0000000..a18d4b6 --- /dev/null +++ b/complimentary_colour_extractor.h @@ -0,0 +1,24 @@ +#ifndef COMPLIMENTARY_COLOUR_EXTRACTOR_H_ +#define COMPLIMENTARY_COLOUR_EXTRACTOR_H_ + +#include "colour_extractor.h" + +class ComplimentaryColourExtractor : public ColourExtractor +{ +private: + // Main colour against which we are finding the complimentary + struct ColourHSV m_main_colour; + +protected: + // Implemented - Evaluates the given colour based on its weight + virtual size_t evaluate_colour(struct ColourHSV hsv, double weight) const; + +public: + ComplimentaryColourExtractor(struct ColourHSV hsv, size_t h_levels = 36, + size_t s_levels = 10, size_t v_levels = 10); + + struct ColourHSV get_main_colour() const; + void set_main_colour(struct ColourHSV hsv); +}; + +#endif // COMPLIMENTARY_COLOUR_EXTRACTOR_H_ diff --git a/main.cpp b/main.cpp index e69de29..10b46b1 100644 --- a/main.cpp +++ b/main.cpp @@ -0,0 +1,103 @@ +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#include "Extractor.h" + +using namespace std; + +void usage(char *name) +{ + cout << "Usage: " << name << " [input image]" << endl; + cout << "Supported image formats: JPG, PNG, TGA, BMP, GIF" << endl; + cout << "Image must have 3 or more channels" << endl; +} + +int main(int argc, char **argv) +{ + int x_size; + int y_size; + int channels; + int error; + unsigned char *image = nullptr; + PixelRGB pixel; + + if (argc < 2) + { + cout << "Not enough arguments" << endl; + usage(argv[0]); + return -1; + } + + error = stbi_info("argv", &x_size, &y_size, &channels); + if (1 != error) + { + cout << "Image type is not supported" << endl; + usage(argv[0]); + return -1; + } + + if (channels < 3) + { + cout << "This image is in grayscale (it has no colour)" << endl; + usage(argv[0]); + return -1; + } + + image = stbi_load("argv", &x_size, &y_size, &channels, 0); + if (nullptr == image) + { + cout << "Could not open image" << endl; + return -1; + } + + for (int i = 0; i < x_size * y_size * channels; i += channels) + { + pixel.r = image[i + 0]; + pixel.g = image[i + 1]; + pixel.b = image[i + 2]; + + if (channels > 3) + { + // TODO: Handle alpha here + } + + // TODO: Add pixel to evaluator here + } + stbi_image_free(image); + + // TODO: Get main and comp colours here + // TODO: Print them to console + + x_size = 16; + y_size = 16; + channels = 3; + image = new unsigned char[x_size * y_size * channels]; + if (nullptr == image) + { + cout << "Could not allocate space for image" << endl; + return -1; + } + + for (int i = 0; i < x_size * y_size; ++i) + { + // TODO: Fill image with pixel data here + } + + stbi_write_png("main.png", x_size, y_size, channels, image, x_size * channels); + + for (int i = 0; i < x_size * y_size; ++i) + { + // TODO: Fill image with pixel data here + } + + stbi_write_png("comp.png", x_size, y_size, channels, image, x_size * channels); + + delete[] image; + + return 0; +} diff --git a/main_colour_extractor.cpp b/main_colour_extractor.cpp new file mode 100644 index 0000000..65a8e61 --- /dev/null +++ b/main_colour_extractor.cpp @@ -0,0 +1,51 @@ +#include "main_colour_extractor.h" + +#include + +// CONSTRUCTORS +MainColourExtractor::MainColourExtractor(double lightness_threshold, + size_t h_levels, size_t s_levels, size_t v_levels) +:ColourExtractor(h_levels, s_levels, v_levels), + m_lightness_threshold(lightness_threshold) +{ +} + +// PRIVATE FUNCS +double MainColourExtractor::calculate_lightness(struct ColourHSV hsv) const +{ + double result = 0.0; + struct ColourRGB rgb; + + rgb = HSVtoRGB(hsv); + result = 0.299F * rgb.r + 0.587F * rgb.g + 0.114F * rgb.b; + + return result; +} + +// PROTECTED FUNCS +size_t MainColourExtractor::evaluate_colour(struct ColourHSV hsv, double weight) + const +{ + size_t result = 0; + double lightness; + + lightness = calculate_lightness(hsv); + if (lightness >= m_lightness_threshold) + { + result = static_cast(round(1000.0 * (pow(weight, 0.3) + + 0.009999999776482582) * hsv.v * (hsv.s + 0.1))); + } + + return result; +} + +// PUBLIC FUNCS +double MainColourExtractor::get_lightness_threshold() const +{ + return m_lightness_threshold; +} + +void MainColourExtractor::set_lightness_threshold(double value) +{ + m_lightness_threshold = value; +} diff --git a/main_colour_extractor.h b/main_colour_extractor.h new file mode 100644 index 0000000..d9bf538 --- /dev/null +++ b/main_colour_extractor.h @@ -0,0 +1,28 @@ +#ifndef MAIN_COLOUR_EXTRACTOR_H_ +#define MAIN_COLOUR_EXTRACTOR_H_ + +#include "colour_extractor.h" + +class MainColourExtractor : public ColourExtractor +{ +private: + // The minimum lightness a colour needs to have to be considered for + // evaluation + double m_lightness_threshold; + + // Calculates the lightness of a colour + double calculate_lightness(struct ColourHSV hsv) const; + +protected: + // Implemented - Evaluates the given colour based on its weight + virtual size_t evaluate_colour(struct ColourHSV hsv, double weight) const; + +public: + MainColourExtractor(double lightness_threshold = 0.2, size_t h_levels = 36, + size_t s_levels = 10, size_t v_levels = 10); + + double get_lightness_threshold() const; + void set_lightness_threshold(double value); +}; + +#endif // MAIN_COLOUR_EXTRACTOR_H_