Implement XEP-0392: Consistent Color Generation
This commit is contained in:
parent
503de303d7
commit
d818296520
|
@ -3,6 +3,7 @@ using Gtk;
|
||||||
|
|
||||||
using Dino.Entities;
|
using Dino.Entities;
|
||||||
using Xmpp;
|
using Xmpp;
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
namespace Dino.Ui.Util {
|
namespace Dino.Ui.Util {
|
||||||
|
|
||||||
|
@ -18,20 +19,22 @@ private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673
|
||||||
private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"};
|
private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"};
|
||||||
private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"};
|
private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"};
|
||||||
|
|
||||||
|
public static string get_consistent_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false) {
|
||||||
|
uint8[] rgb;
|
||||||
|
if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid.bare_jid, account) && jid.resourcepart != null) {
|
||||||
|
rgb = ConsistentColor.string_to_rgb(jid.resourcepart);
|
||||||
|
} else {
|
||||||
|
rgb = ConsistentColor.string_to_rgb(jid.bare_jid.to_string());
|
||||||
|
}
|
||||||
|
return "%.2x%.2x%.2x".printf(rgb[0], rgb[1], rgb[2]);
|
||||||
|
}
|
||||||
|
|
||||||
public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
||||||
uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash();
|
return get_consistent_hex_color(stream_interactor, account, get_relevant_jid(stream_interactor, account, jid, conversation));
|
||||||
return material_colors_300[hash % material_colors_300.length];
|
|
||||||
// return tango_colors_light[name.hash() % tango_colors_light.length];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false, Conversation? conversation = null) {
|
public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false, Conversation? conversation = null) {
|
||||||
uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash();
|
return get_consistent_hex_color(stream_interactor, account, get_relevant_jid(stream_interactor, account, jid, conversation), dark_theme);
|
||||||
if (dark_theme) {
|
|
||||||
return material_colors_300[hash % material_colors_300.length];
|
|
||||||
} else {
|
|
||||||
return material_colors_500[hash % material_colors_500.length];
|
|
||||||
}
|
|
||||||
// return tango_colors_medium[name.hash() % tango_colors_medium.length];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) {
|
||||||
|
|
|
@ -106,6 +106,9 @@ SOURCES
|
||||||
"src/module/xep/0384_omemo/omemo_encryptor.vala"
|
"src/module/xep/0384_omemo/omemo_encryptor.vala"
|
||||||
"src/module/xep/0384_omemo/omemo_decryptor.vala"
|
"src/module/xep/0384_omemo/omemo_decryptor.vala"
|
||||||
|
|
||||||
|
"src/module/xep/0392_consistent_color/consistent_color.vala"
|
||||||
|
"src/module/xep/0392_consistent_color/hsluv.vala"
|
||||||
|
|
||||||
"src/module/xep/0184_message_delivery_receipts.vala"
|
"src/module/xep/0184_message_delivery_receipts.vala"
|
||||||
"src/module/xep/0191_blocking_command.vala"
|
"src/module/xep/0191_blocking_command.vala"
|
||||||
"src/module/xep/0198_stream_management.vala"
|
"src/module/xep/0198_stream_management.vala"
|
||||||
|
@ -160,7 +163,7 @@ DEPENDS
|
||||||
add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="xmpp-vala")
|
add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="xmpp-vala")
|
||||||
add_library(xmpp-vala SHARED ${ENGINE_VALA_C})
|
add_library(xmpp-vala SHARED ${ENGINE_VALA_C})
|
||||||
add_dependencies(xmpp-vala xmpp-vala-vapi)
|
add_dependencies(xmpp-vala xmpp-vala-vapi)
|
||||||
target_link_libraries(xmpp-vala ${ENGINE_PACKAGES})
|
target_link_libraries(xmpp-vala ${ENGINE_PACKAGES} m)
|
||||||
set_target_properties(xmpp-vala PROPERTIES VERSION 0.1 SOVERSION 0)
|
set_target_properties(xmpp-vala PROPERTIES VERSION 0.1 SOVERSION 0)
|
||||||
|
|
||||||
install(TARGETS xmpp-vala ${TARGET_INSTALL})
|
install(TARGETS xmpp-vala ${TARGET_INSTALL})
|
||||||
|
@ -175,6 +178,7 @@ if(BUILD_TESTS)
|
||||||
|
|
||||||
"tests/jid.vala"
|
"tests/jid.vala"
|
||||||
"tests/stanza.vala"
|
"tests/stanza.vala"
|
||||||
|
"tests/color.vala"
|
||||||
"tests/util.vala"
|
"tests/util.vala"
|
||||||
CUSTOM_VAPIS
|
CUSTOM_VAPIS
|
||||||
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
|
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
namespace Xmpp.Xep.ConsistentColor {
|
||||||
|
private const double KR = 0.299;
|
||||||
|
private const double KG = 0.587;
|
||||||
|
private const double KB = 0.114;
|
||||||
|
private const double Y = 0.732;
|
||||||
|
|
||||||
|
public float string_to_angle(string s) {
|
||||||
|
Checksum checksum = new Checksum(ChecksumType.SHA1);
|
||||||
|
checksum.update(s.data, -1);
|
||||||
|
size_t len = 20;
|
||||||
|
uint8[] digest = new uint8[len];
|
||||||
|
checksum.get_digest(digest, ref len);
|
||||||
|
uint16 output = ((uint16)(*(uint16*)digest)).to_little_endian();
|
||||||
|
return (((float) output) / 65536.0f) * 360.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint8[] rgbd_to_rgb(double[] rgbd) {
|
||||||
|
return {(uint8)(rgbd[0] * 255.0), (uint8)(rgbd[1] * 255.0), (uint8)(rgbd[2] * 255.0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] rgbd_to_rgbf(double[] rgbd) {
|
||||||
|
return {(float)rgbd[0], (float)rgbd[1], (float)rgbd[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double[] angle_to_rgbd(double angle) {
|
||||||
|
return Hsluv.hsluv_to_rgb(new double[] {angle, 100, 50});
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] string_to_rgbf(string s) {
|
||||||
|
return rgbd_to_rgbf(angle_to_rgbd(string_to_angle(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint8[] string_to_rgb(string s) {
|
||||||
|
return rgbd_to_rgb(angle_to_rgbd(string_to_angle(s)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
393
xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala
Normal file
393
xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala
Normal file
|
@ -0,0 +1,393 @@
|
||||||
|
namespace Hsluv {
|
||||||
|
|
||||||
|
private const double[] M0 = { 3.240969941904521, -1.537383177570093, -0.498610760293 };
|
||||||
|
private const double[] M1 = { -0.96924363628087, 1.87596750150772, 0.041555057407175 };
|
||||||
|
private const double[] M2 = { 0.055630079696993, -0.20397695888897, 1.056971514242878 };
|
||||||
|
|
||||||
|
private const double[] MInv0 = { 0.41239079926595, 0.35758433938387, 0.18048078840183 };
|
||||||
|
private const double[] MInv1 = { 0.21263900587151, 0.71516867876775, 0.072192315360733 };
|
||||||
|
private const double[] MInv2 = { 0.019330818715591, 0.11919477979462, 0.95053215224966 };
|
||||||
|
|
||||||
|
private double RefX = 0.95045592705167;
|
||||||
|
private double RefY = 1.0;
|
||||||
|
private double RefZ = 1.089057750759878;
|
||||||
|
|
||||||
|
private double RefU = 0.19783000664283;
|
||||||
|
private double RefV = 0.46831999493879;
|
||||||
|
|
||||||
|
private double Kappa = 903.2962962;
|
||||||
|
private double Epsilon = 0.0088564516;
|
||||||
|
|
||||||
|
private struct Bounds {
|
||||||
|
double t0;
|
||||||
|
double t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bounds get_bounds_sub(double L, double sub1, double sub2, int t, double[] m) {
|
||||||
|
double m1 = m[0];
|
||||||
|
double m2 = m[1];
|
||||||
|
double m3 = m[2];
|
||||||
|
double top1 = (284517 * m1 - 94839 * m3) * sub2;
|
||||||
|
double top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * L * sub2 - 769860 * t * L;
|
||||||
|
double bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t;
|
||||||
|
return { top1 / bottom, top2 / bottom };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bounds[] get_bounds(double L) {
|
||||||
|
double sub1 = Math.pow(L + 16, 3) / 1560896;
|
||||||
|
double sub2 = sub1 > Epsilon ? sub1 : L / Kappa;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get_bounds_sub(L, sub1, sub2, 0, M0),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 1, M0),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 0, M1),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 1, M1),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 0, M2),
|
||||||
|
get_bounds_sub(L, sub1, sub2, 1, M2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double intersect_line_line(double[] lineA, double[] lineB) {
|
||||||
|
return (lineA[1] - lineB[1]) / (lineB[0] - lineA[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double distance_from_pole(double[] point) {
|
||||||
|
return Math.sqrt(Math.pow(point[0], 2) + Math.pow(point[1], 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool length_of_ray_until_intersect(double theta, Bounds line, out double length) {
|
||||||
|
length = line.t1 / (Math.sin(theta) - line.t0 * Math.cos(theta));
|
||||||
|
|
||||||
|
return length >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double max_safe_chroma_for_l(double L) {
|
||||||
|
Bounds[] bounds = get_bounds(L);
|
||||||
|
double min = double.MAX;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
var m1 = bounds[i].t0;
|
||||||
|
var b1 = bounds[i].t1;
|
||||||
|
var line = new double[] { m1, b1 };
|
||||||
|
|
||||||
|
double x = intersect_line_line(line, new double[] {-1 / m1, 0 });
|
||||||
|
double length = distance_from_pole(new double[] { x, b1 + x * m1 });
|
||||||
|
|
||||||
|
min = double.min(min, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double max_chroma_for_lh(double L, double H) {
|
||||||
|
double hrad = H / 360 * Math.PI * 2;
|
||||||
|
|
||||||
|
Bounds[] bounds = get_bounds(L);
|
||||||
|
double min = double.MAX;
|
||||||
|
|
||||||
|
foreach (var bound in bounds) {
|
||||||
|
double length;
|
||||||
|
|
||||||
|
if (length_of_ray_until_intersect(hrad, bound, out length)) {
|
||||||
|
min = double.min(min, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double dot_product(double[] a, double[] b) {
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < a.length; ++i) {
|
||||||
|
sum += a[i] * b[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double round(double value, int places) {
|
||||||
|
double n = Math.pow(10, places);
|
||||||
|
|
||||||
|
return Math.round(value * n) / n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double from_linear(double c) {
|
||||||
|
if (c <= 0.0031308) {
|
||||||
|
return 12.92 * c;
|
||||||
|
} else {
|
||||||
|
return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double to_linear(double c) {
|
||||||
|
if (c > 0.04045) {
|
||||||
|
return Math.pow((c + 0.055) / (1 + 0.055), 2.4);
|
||||||
|
} else {
|
||||||
|
return c / 12.92;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] rgb_prepare(double[] tuple) {
|
||||||
|
for (int i = 0; i < tuple.length; ++i) {
|
||||||
|
tuple[i] = round(tuple[i], 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tuple.length; ++i) {
|
||||||
|
double ch = tuple[i];
|
||||||
|
|
||||||
|
if (ch < -0.0001 || ch > 1.0001) {
|
||||||
|
return null; //throw new Error("Illegal rgb value: " + ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = new int[tuple.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < tuple.length; ++i) {
|
||||||
|
results[i] = (int) Math.round(tuple[i] * 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] xyz_to_rgb(double[] tuple) {
|
||||||
|
return new double[] {
|
||||||
|
from_linear(dot_product(M0, tuple)),
|
||||||
|
from_linear(dot_product(M1, tuple)),
|
||||||
|
from_linear(dot_product(M2, tuple))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_xyz(double[] tuple) {
|
||||||
|
var rgbl = new double[] {
|
||||||
|
to_linear(tuple[0]),
|
||||||
|
to_linear(tuple[1]),
|
||||||
|
to_linear(tuple[2])
|
||||||
|
};
|
||||||
|
|
||||||
|
return new double[] {
|
||||||
|
dot_product(MInv0, rgbl),
|
||||||
|
dot_product(MInv1, rgbl),
|
||||||
|
dot_product(MInv2, rgbl)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double y_to_l(double Y) {
|
||||||
|
if (Y <= Epsilon) {
|
||||||
|
return (Y / RefY) * Kappa;
|
||||||
|
} else {
|
||||||
|
return 116 * Math.pow(Y / RefY, 1.0 / 3.0) - 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double l_to_y(double L) {
|
||||||
|
if (L <= 8) {
|
||||||
|
return RefY * L / Kappa;
|
||||||
|
} else {
|
||||||
|
return RefY * Math.pow((L + 16) / 116, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] xyz_to_luv(double[] tuple) {
|
||||||
|
double X = tuple[0];
|
||||||
|
double Y = tuple[1];
|
||||||
|
double Z = tuple[2];
|
||||||
|
|
||||||
|
double varU = (4 * X) / (X + (15 * Y) + (3 * Z));
|
||||||
|
double varV = (9 * Y) / (X + (15 * Y) + (3 * Z));
|
||||||
|
|
||||||
|
double L = y_to_l(Y);
|
||||||
|
|
||||||
|
if (L == 0) {
|
||||||
|
return new double[] { 0, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
var U = 13 * L * (varU - RefU);
|
||||||
|
var V = 13 * L * (varV - RefV);
|
||||||
|
|
||||||
|
return new double [] { L, U, V };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] luv_to_xyz(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double U = tuple[1];
|
||||||
|
double V = tuple[2];
|
||||||
|
|
||||||
|
if (L == 0) {
|
||||||
|
return new double[] { 0, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
double varU = U / (13 * L) + RefU;
|
||||||
|
double varV = V / (13 * L) + RefV;
|
||||||
|
|
||||||
|
double Y = l_to_y(L);
|
||||||
|
double X = 0 - (9 * Y * varU) / ((varU - 4) * varV - varU * varV);
|
||||||
|
double Z = (9 * Y - (15 * varV * Y) - (varV * X)) / (3 * varV);
|
||||||
|
|
||||||
|
return new double[] { X, Y, Z };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] luv_to_lch(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double U = tuple[1];
|
||||||
|
double V = tuple[2];
|
||||||
|
|
||||||
|
double C = Math.pow(Math.pow(U, 2) + Math.pow(V, 2), 0.5);
|
||||||
|
double Hrad = Math.atan2(V, U);
|
||||||
|
|
||||||
|
double H = Hrad * 180.0 / Math.PI;
|
||||||
|
|
||||||
|
if (H < 0) {
|
||||||
|
H = 360 + H;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new double[] { L, C, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_luv(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double C = tuple[1];
|
||||||
|
double H = tuple[2];
|
||||||
|
|
||||||
|
double Hrad = H / 360.0 * 2 * Math.PI;
|
||||||
|
double U = Math.cos(Hrad) * C;
|
||||||
|
double V = Math.sin(Hrad) * C;
|
||||||
|
|
||||||
|
return new double [] { L, U, V };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hsluv_to_lch(double[] tuple) {
|
||||||
|
double H = tuple[0];
|
||||||
|
double S = tuple[1];
|
||||||
|
double L = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { 100, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { 0, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_chroma_for_lh(L, H);
|
||||||
|
double C = max / 100 * S;
|
||||||
|
|
||||||
|
return new double[] { L, C, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_hsluv(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double C = tuple[1];
|
||||||
|
double H = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { H, 0, 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { H, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_chroma_for_lh(L, H);
|
||||||
|
double S = C / max * 100;
|
||||||
|
|
||||||
|
return new double[] { H, S, L };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hpluv_to_lch(double[] tuple) {
|
||||||
|
double H = tuple[0];
|
||||||
|
double S = tuple[1];
|
||||||
|
double L = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { 100, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { 0, 0, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_safe_chroma_for_l(L);
|
||||||
|
double C = max / 100 * S;
|
||||||
|
|
||||||
|
return new double[] { L, C, H };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_hpluv(double[] tuple) {
|
||||||
|
double L = tuple[0];
|
||||||
|
double C = tuple[1];
|
||||||
|
double H = tuple[2];
|
||||||
|
|
||||||
|
if (L > 99.9999999) {
|
||||||
|
return new double[] { H, 0, 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L < 0.00000001) {
|
||||||
|
return new double[] { H, 0, 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max = max_safe_chroma_for_l(L);
|
||||||
|
double S = C / max * 100;
|
||||||
|
|
||||||
|
return new double[] { H, S, L };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string rgb_to_hex(double[] tuple) {
|
||||||
|
int[] prepared = rgb_prepare(tuple);
|
||||||
|
|
||||||
|
return "#%.2x%.2x%.2x".printf(prepared[0], prepared[1], prepared[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hex_to_tgb(string hex) {
|
||||||
|
return new double[] {
|
||||||
|
hex.substring(1, 2).to_long(null, 16) / 255.0,
|
||||||
|
hex.substring(3, 2).to_long(null, 16) / 255.0,
|
||||||
|
hex.substring(5, 2).to_long(null, 16) / 255.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] lch_to_rgb(double[] tuple) {
|
||||||
|
return xyz_to_rgb(luv_to_xyz(lch_to_luv(tuple)));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_lch(double[] tuple) {
|
||||||
|
return luv_to_lch(xyz_to_luv(rgb_to_xyz(tuple)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rgb <--> Hsluv(p)
|
||||||
|
|
||||||
|
internal double[] hsluv_to_rgb(double[] tuple) {
|
||||||
|
return lch_to_rgb(hsluv_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_hsluv(double[] tuple) {
|
||||||
|
return lch_to_hsluv(rgb_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hpluv_to_rgb(double[] tuple) {
|
||||||
|
return lch_to_rgb(hpluv_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] rgb_to_hpluv(double[] tuple) {
|
||||||
|
return lch_to_hpluv(rgb_to_lch(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex
|
||||||
|
|
||||||
|
internal string hsluv_to_hex(double[] tuple) {
|
||||||
|
return rgb_to_hex(hsluv_to_rgb(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string hpluv_to_hex(double[] tuple) {
|
||||||
|
return rgb_to_hex(hpluv_to_rgb(tuple));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hex_to_hsluv(string s) {
|
||||||
|
return rgb_to_hsluv(hex_to_tgb(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal double[] hex_to_hpluv(string s) {
|
||||||
|
return rgb_to_hpluv(hex_to_tgb(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
50
xmpp-vala/tests/color.vala
Normal file
50
xmpp-vala/tests/color.vala
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using Xmpp.Xep;
|
||||||
|
|
||||||
|
namespace Xmpp.Test {
|
||||||
|
|
||||||
|
class ColorTest : Gee.TestCase {
|
||||||
|
|
||||||
|
public ColorTest() {
|
||||||
|
base("color");
|
||||||
|
|
||||||
|
add_test("xep-vectors-angle", () => { text_xep_vectors_angle(); });
|
||||||
|
add_test("xep-vectors-rgbf", () => { test_xep_vectors_rgbf(); });
|
||||||
|
add_test("rgb-to-angle", () => { test_rgb_to_angle(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
public void text_xep_vectors_angle() {
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("Romeo"), 327.255249);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("juliet@capulet.lit"), 209.410400);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("😺"), 331.199341);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("council"), 359.994507);
|
||||||
|
fail_if_not_eq_double(ConsistentColor.string_to_angle("Board"), 171.430664);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool fail_if_not_eq_rgbf(float[] left, float[] right) {
|
||||||
|
bool failed = false;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
failed = fail_if_not_eq_float(left[i], right[i]) || failed;
|
||||||
|
}
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test_xep_vectors_rgbf() {
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("Romeo"), {0.865f,0.000f,0.686f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("juliet@capulet.lit"), {0.000f,0.515f,0.573f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("😺"), {0.872f,0.000f,0.659f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("council"), {0.918f,0.000f,0.394f});
|
||||||
|
fail_if_not_eq_rgbf(ConsistentColor.string_to_rgbf("Board"), {0.000f,0.527f,0.457f});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void test_rgb_to_angle() {
|
||||||
|
string[] colors = {"e57373", "f06292", "ba68c8", "9575cd", "7986cb", "64b5f6", "4fc3f7", "4dd0e1", "4db6ac", "81c784", "aed581", "dce775", "fff176", "ffd54f", "ffb74d", "ff8a65"};
|
||||||
|
foreach(string hex_color in colors) {
|
||||||
|
uint8 r = (uint8) ((double) hex_color.substring(0, 2).to_long(null, 16));
|
||||||
|
uint8 g = (uint8) ((double) hex_color.substring(2, 2).to_long(null, 16));
|
||||||
|
uint8 b = (uint8) ((double) hex_color.substring(4, 2).to_long(null, 16));
|
||||||
|
//print(@"$hex_color, $r, $g, $b, $(ConsistentColor.rgb_to_angle(r, g, b))\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ int main(string[] args) {
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.UtilTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.UtilTest().get_suite());
|
||||||
TestSuite.get_root().add_suite(new Xmpp.Test.JidTest().get_suite());
|
TestSuite.get_root().add_suite(new Xmpp.Test.JidTest().get_suite());
|
||||||
|
TestSuite.get_root().add_suite(new Xmpp.Test.ColorTest().get_suite());
|
||||||
return GLib.Test.run();
|
return GLib.Test.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +69,22 @@ bool fail_if_not_eq_int(int left, int right, string? reason = null) {
|
||||||
return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right");
|
return fail_if_not(left == right, @"$(reason + ": " ?? "")$left != $right");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float float_to_accuracy(float f, float accuracy) {
|
||||||
|
return (float) (Math.round(f * Math.pow(10, accuracy)) / Math.pow(10, accuracy));
|
||||||
|
}
|
||||||
|
|
||||||
|
private float double_to_accuracy(double f, float accuracy) {
|
||||||
|
return (float) (Math.round(f * Math.pow(10, accuracy)) / Math.pow(10, accuracy));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fail_if_not_eq_float(float left, float right, float accuracy = 3, string? reason = null) {
|
||||||
|
return fail_if_not(float_to_accuracy(left, accuracy) == float_to_accuracy(right, accuracy), @"$(reason + ": " ?? "")$left != $right");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fail_if_not_eq_double(double left, double right, float accuracy = 3, string? reason = null) {
|
||||||
|
return fail_if_not(double_to_accuracy(left, accuracy) == double_to_accuracy(right, accuracy), @"$(reason + ": " ?? "")$left != $right");
|
||||||
|
}
|
||||||
|
|
||||||
bool fail_if_not_eq_str(string? left, string? right, string? reason = null) {
|
bool fail_if_not_eq_str(string? left, string? right, string? reason = null) {
|
||||||
bool nullcheck = (left == null || right == null) && (left != null && right != null);
|
bool nullcheck = (left == null || right == null) && (left != null && right != null);
|
||||||
if (left == null) left = "(null)";
|
if (left == null) left = "(null)";
|
||||||
|
|
Loading…
Reference in a new issue