gala/lib/Drawing/Color.vala

560 lines
19 KiB
Vala
Raw Normal View History

/*
* Copyright 2019-2021 elementary, Inc. (https://elementary.io)
* Copyright 20112013 Robert Dyer
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
using Gdk;
namespace Gala.Drawing {
/**
* A class containing an RGBA color and methods for more powerful color manipulation.
*/
2024-03-26 20:05:32 +03:00
public class Color : GLib.Object {
2024-02-28 19:31:22 +03:00
public const Gdk.RGBA LIGHT_BACKGROUND = { (250f / 255f), (250f / 255f), (250f / 255f), 1};
public const Gdk.RGBA DARK_BACKGROUND = { (51 / 255f), (51 / 255f), (51 / 255f), 1};
public const Gdk.RGBA LIGHT_BORDER = { 0, 0, 0, 0.2};
public const Gdk.RGBA DARK_BORDER = { 0, 0, 0, 0.75};
public const Gdk.RGBA LIGHT_HIGHLIGHT = { 255, 255, 255, 1};
public const Gdk.RGBA DARK_HIGHLIGHT = { 255, 255, 255, 0.05};
public const Gdk.RGBA TOOLTIP_BACKGROUND = { 0, 0, 0, 1};
public const Gdk.RGBA TOOLTIP_TEXT_COLOR = { 1, 1, 1, 1};
/**
* The value of the red channel, with 0 being the lowest value and 1.0 being the greatest value.
*/
public double R; // vala-lint=naming-convention
/**
* The value of the green channel, with 0 being the lowest value and 1.0 being the greatest value.
*/
public double G; // vala-lint=naming-convention
/**
* The value of the blue channel, with 0 being the lowest value and 1.0 being the greatest value.
*/
public double B; // vala-lint=naming-convention
/**
* The value of the alpha channel, with 0 being the lowest value and 1.0 being the greatest value.
*/
public double A; // vala-lint=naming-convention
/**
* Extracts the alpha value from the integer value
* serialized by {@link Gala.Drawing.Color.to_int}.
*
* @return the alpha channel value as a uint8 ranging from 0 - 255.
*/
public static uint8 alpha_from_int (int color) {
return (uint8)((color >> 24) & 0xFF);
}
/**
* Extracts the red value from the integer value
* serialized by {@link Gala.Drawing.Color.to_int}.
*
* @return the red channel value as a uint8 ranging from 0 - 255.
*/
public static uint8 red_from_int (int color) {
return (uint8)((color >> 16) & 0xFF);
}
/**
* Extracts the green value from the integer value
* serialized by {@link Gala.Drawing.Color.to_int}.
*
* @return the green channel value as a uint8 ranging from 0 - 255.
*/
public static uint8 green_from_int (int color) {
return (uint8)((color >> 8) & 0xFF);
}
/**
* Extracts the blue value from the integer value
* serialized by {@link Gala.Drawing.Color.to_int}.
*
* @return the blue channel value as a uint8 ranging from 0 - 255.
*/
public static uint8 blue_from_int (int color) {
return (uint8)(color & 0xFF);
}
/**
* Constructs a new {@link Gala.Drawing.Color} with the supplied values.
*
* @param R the value of the red channel as a double
* @param G the value of the green channel as a double
* @param B the value of the blue channel as a double
* @param A the value of the alpha channel as a double
*/
public Color (double R, double G, double B, double A) { // vala-lint=naming-convention
this.R = R;
this.G = G;
this.B = B;
this.A = A;
}
/**
* Constructs a new {@link Gala.Drawing.Color} from a {@link Gdk.RGBA}.
*
* @param color the {@link Gdk.RGBA}
*/
public Color.from_rgba (Gdk.RGBA color) {
set_from_rgba (color);
}
/**
* Constructs a new {@link Gala.Drawing.Color} from a string.
*
* The string can be either one of:
*
* * A standard name (Taken from the X11 rgb.txt file).
* * A hexadecimal value in the form #rgb, #rrggbb, #rrrgggbbb or #rrrrggggbbbb
* * A RGB color in the form rgb(r,g,b) (In this case the color will have full opacity)
* * A RGBA color in the form rgba(r,g,b,a)
*
* For more details on formatting and how this function works see {@link Gdk.RGBA.parse}
*
* @param color the string specifying the color
*/
public Color.from_string (string color) {
Gdk.RGBA rgba = Gdk.RGBA ();
rgba.parse (color);
set_from_rgba (rgba);
}
/**
* Constructs a new {@link Gala.Drawing.Color} from an integer.
*
* This constructor should be used when deserializing the previously serialized
* color by {@link Gala.Drawing.Color.to_int}.
*
* For more details on what format the color integer representation has, see {@link Gala.Drawing.Color.to_int}.
*
* If you would like to deserialize the A, R, G and B values from the integer without
* creating a new instance of {@link Gala.Drawing.Color}, you can use the available
* //*_from_int// static method collection such as {@link Gala.Drawing.Color.alpha_from_int}.
*
* @param color the integer specyfying the color
*/
public Color.from_int (int color) {
R = (double)red_from_int (color) / (double)uint8.MAX;
G = (double)green_from_int (color) / (double)uint8.MAX;
B = (double)blue_from_int (color) / (double)uint8.MAX;
A = (double)alpha_from_int (color) / (double)uint8.MAX;
}
/**
* Changes the hue of this color to the supplied one.
*
* @param hue the hue to change this color to
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_hue (double hue) requires (hue >= 0 && hue <= 360) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
h = hue;
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Changes the saturation of this color to the supplied one.
*
* @param sat the saturation to change this color to
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_sat (double sat) requires (sat >= 0 && sat <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
s = sat;
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Changes the value of this color to the supplied one.
*
* @param val the value to change this color to
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_val (double val) requires (val >= 0 && val <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
v = val;
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Changes the value of the alpha channel.
*
* @param alpha the value of the alpha channel
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_alpha (double alpha) requires (alpha >= 0 && alpha <= 1) {
A = alpha;
return this;
}
/**
* Get the value.
* @return the hue of this color, as a double value
*/
public double get_hue () {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
return h;
}
/**
* Get the value.
* @return the saturation of this color, as a double value
*/
public double get_sat () {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
return s;
}
/**
* Get the value.
*
* @return the value of this color, as a double value
*/
public double get_val () {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
return v;
}
/**
* Adds the supplied hue value to this color's hue value.
*
* @param val the hue to add to this color's hue
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color add_hue (double val) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
h = (((h + val) % 360) + 360) % 360;
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Changes this color's saturation to the supplied saturation, if it is greater than this color's saturation.
*
* @param sat the saturation to change this color to
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_min_sat (double sat) requires (sat >= 0 && sat <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
s = double.max (s, sat);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Changes this color's value to the supplied value, if it is greater than this color's value.
*
* @param val the value to change this color to
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_min_value (double val) requires (val >= 0 && val <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
v = double.max (v, val);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Changes this color's saturation to the supplied saturation, if it is smaller than this color's saturation.
*
* @param sat the hue to change this color to
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_max_sat (double sat) requires (sat >= 0 && sat <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
s = double.min (s, sat);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Changes this color's value to the supplied value, if it is smaller than this color's value.
*
* @param val the value to change this color to
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color set_max_val (double val) requires (val >= 0 && val <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
v = double.min (v, val);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Multiplies this color's saturation by the supplied amount.
*
* @param amount the amount to multiply the saturation by
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color multiply_sat (double amount) requires (amount >= 0) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
s = double.min (1, s * amount);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Brightens this color's value by the supplied amount.
*
* @param amount the amount to brighten the value by
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color brighten_val (double amount) requires (amount >= 0 && amount <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
v = double.min (1, v + (1 - v) * amount);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Darkens this color's value by the supplied amount.
*
* @param amount the amount to darken the value by
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color darken_val (double amount) requires (amount >= 0 && amount <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
v = double.max (0, v - (1 - v) * amount);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
/**
* Darkens this color's value by the supplied amount * color's saturation.
*
* @param amount the amount to darken the value by
*
* @return the new {@link Gala.Drawing.Color}
*/
public Color darken_by_sat (double amount) requires (amount >= 0 && amount <= 1) {
double h, s, v;
rgb_to_hsv (R, G, B, out h, out s, out v);
v = double.max (0, v - amount * s);
hsv_to_rgb (h, s, v, out R, out G, out B);
return this;
}
private void rgb_to_hsv (
double r, double g, double b, out double h, out double s, out double v
) requires (r >= 0 && r <= 1) requires (g >= 0 && g <= 1) requires (b >= 0 && b <= 1) {
var min = double.min (r, double.min (g, b));
var max = double.max (r, double.max (g, b));
v = max;
if (v == 0) {
h = 0;
s = 0;
return;
}
// normalize value to 1
r /= v;
g /= v;
b /= v;
min = double.min (r, double.min (g, b));
max = double.max (r, double.max (g, b));
var delta = max - min;
s = delta;
if (s == 0) {
h = 0;
return;
}
// normalize saturation to 1
r = (r - min) / delta;
g = (g - min) / delta;
b = (b - min) / delta;
if (max == r) {
h = 0 + 60 * (g - b);
if (h < 0)
h += 360;
} else if (max == g) {
h = 120 + 60 * (b - r);
} else {
h = 240 + 60 * (r - g);
}
}
private void hsv_to_rgb (
double h, double s, double v, out double r, out double g, out double b
) requires (h >= 0 && h <= 360) requires (s >= 0 && s <= 1) requires (v >= 0 && v <= 1) {
r = 0;
g = 0;
b = 0;
if (s == 0) {
r = v;
g = v;
b = v;
} else {
var sec_num = (int) Math.floor (h / 60);
var frac_sec = h / 60.0 - sec_num;
var p = v * (1 - s);
var q = v * (1 - s * frac_sec);
var t = v * (1 - s * (1 - frac_sec));
switch (sec_num) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
}
}
/**
* {@inheritDoc}
*/
public string settings_serialize () {
return "%d;;%d;;%d;;%d".printf ((int) (R * uint8.MAX),
(int) (G * uint8.MAX),
(int) (B * uint8.MAX),
(int) (A * uint8.MAX));
}
/**
* {@inheritDoc}
*/
public void settings_deserialize (string s) {
var parts = s.split (";;");
R = double.min (uint8.MAX, double.max (0, int.parse (parts [0]))) / uint8.MAX;
G = double.min (uint8.MAX, double.max (0, int.parse (parts [1]))) / uint8.MAX;
B = double.min (uint8.MAX, double.max (0, int.parse (parts [2]))) / uint8.MAX;
A = double.min (uint8.MAX, double.max (0, int.parse (parts [3]))) / uint8.MAX;
}
/**
* Returns a textual specification of this in the form `rgb (r, g, b)` or `rgba (r, g, b, a)`,
* where r, g, b and a represent the red, green, blue and alpha values respectively.
*
* r, g, and b are represented as integers in the range 0 to 255, and a is represented as
* floating point value in the range 0 to 1.
*
* Note: that this string representation may lose some precision, since r, g and b are represented
* as 8-bit integers. If this is a concern, you should use a different representation.
*
* This returns the same string as a {@link Gdk.RGBA} would return in {@link Gdk.RGBA.to_string}
*
* @return the text string
*/
public string to_string () {
Gdk.RGBA rgba = {(float)R, (float)G, (float)B, (float)A};
return rgba.to_string ();
}
/**
* Converts this to a 32 bit integer.
*
* This function can be useful for serializing the color so that it can be stored
* and retrieved easily with hash tables and lists.
*
* The returned integer will contain the four channels
* that define the {@link Gala.Drawing.Color} class: alpha, red, green and blue.
*
* Each channel is represented by 8 bits.
* The first 8 bits of the integer conatin the alpha channel while all other 24 bits represent
* red, green and blue channels respectively.
*
* The format written as a string would look like this:
*
* //AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB//
*
* where //A// is one bit of alpha chnnel, //R// of red channel, //G// of green channel and //B// of blue channel.
*
* @return a 32 bit integer representing this
*/
public int to_int () {
uint8 red = (uint8)(R * uint8.MAX);
uint8 green = (uint8)(G * uint8.MAX);
uint8 blue = (uint8)(B * uint8.MAX);
uint8 alpha = (uint8)(A * uint8.MAX);
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
private void set_from_rgba (Gdk.RGBA color) {
R = color.red;
G = color.green;
B = color.blue;
A = color.alpha;
}
}
}