You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@baremaps.apache.org by bc...@apache.org on 2023/06/18 12:04:41 UTC

[incubator-baremaps] 01/01: Refactor the basemap and introduce new themes

This is an automated email from the ASF dual-hosted git repository.

bchapuis pushed a commit to branch basemap-refactoring
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git

commit dc6a8a527a4d800f9ed761c5a765b8f9be144d26
Author: Bertil Chapuis <bc...@gmail.com>
AuthorDate: Sun Jun 18 14:04:27 2023 +0200

    Refactor the basemap and introduce new themes
---
 basemap/theme.js                 |   2 +-
 basemap/themes/achromatomaly.js  |  13 ++
 basemap/themes/achromatopsia.js  |  13 ++
 basemap/themes/contrast.js       |  13 ++
 basemap/themes/dark.js           |  13 ++
 basemap/themes/default.js        |   3 -
 basemap/themes/deuteranomaly.js  |  13 ++
 basemap/themes/deuteranopia.js   |  13 ++
 basemap/themes/grayscale.js      |  13 ++
 basemap/themes/light.js          |  58 ++-----
 basemap/themes/positron.js       |  18 ---
 basemap/themes/protanomaly.js    |  13 ++
 basemap/themes/protanopia.js     |  13 ++
 basemap/themes/road.js           |  15 --
 basemap/themes/tools.js          |  36 -----
 basemap/themes/transportation.js |  20 +++
 basemap/themes/tritanomaly.js    |  13 ++
 basemap/themes/tritanopia.js     |  13 ++
 basemap/utils/color.js           | 336 ++++++++++++++++++++++++++++++++++-----
 19 files changed, 471 insertions(+), 160 deletions(-)

diff --git a/basemap/theme.js b/basemap/theme.js
index 00a70be1..87c99b84 100644
--- a/basemap/theme.js
+++ b/basemap/theme.js
@@ -1,3 +1,3 @@
-import theme from "./themes/default.js";
+import theme from "./themes/transportation.js";
 
 export default theme;
\ No newline at end of file
diff --git a/basemap/themes/achromatomaly.js b/basemap/themes/achromatomaly.js
new file mode 100644
index 00000000..11430509
--- /dev/null
+++ b/basemap/themes/achromatomaly.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Achromatomaly).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/achromatopsia.js b/basemap/themes/achromatopsia.js
new file mode 100644
index 00000000..e593b603
--- /dev/null
+++ b/basemap/themes/achromatopsia.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Achromatopsia).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/contrast.js b/basemap/themes/contrast.js
new file mode 100644
index 00000000..f6cbee6a
--- /dev/null
+++ b/basemap/themes/contrast.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.contrast(0.1).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/dark.js b/basemap/themes/dark.js
new file mode 100644
index 00000000..e4c173ef
--- /dev/null
+++ b/basemap/themes/dark.js
@@ -0,0 +1,13 @@
+import style from './light.js';
+import {Color} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.grayscale().invert().toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/default.js b/basemap/themes/default.js
index a6173d41..87ef3b61 100644
--- a/basemap/themes/default.js
+++ b/basemap/themes/default.js
@@ -1,5 +1,4 @@
 export default {
-
     aerialwayStyleLinePaintLineColor: 'rgb(177, 177, 175)',
     aerowayLineRunwayLineColor: 'rgba(187, 187, 204, 1.0)',
     aerowayLineTaxiwayLineColor: 'rgba(187, 187, 204, 1.0)',
@@ -607,6 +606,4 @@ export default {
     waterwayLineWaterwayLineColor: 'rgb(170, 211, 223)',
     waterwayTunnelCasingLineColor: 'rgb(170, 211, 223)',
     waterwayTunnelLineLineColor: 'rgb(243, 247, 247)'
-
-
 };
diff --git a/basemap/themes/deuteranomaly.js b/basemap/themes/deuteranomaly.js
new file mode 100644
index 00000000..d82134d9
--- /dev/null
+++ b/basemap/themes/deuteranomaly.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Deuteranomaly).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/deuteranopia.js b/basemap/themes/deuteranopia.js
new file mode 100644
index 00000000..414e201a
--- /dev/null
+++ b/basemap/themes/deuteranopia.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Deuteranopia).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/grayscale.js b/basemap/themes/grayscale.js
new file mode 100644
index 00000000..53e42682
--- /dev/null
+++ b/basemap/themes/grayscale.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.grayscale().toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/light.js b/basemap/themes/light.js
index 2027d9e0..547014ed 100644
--- a/basemap/themes/light.js
+++ b/basemap/themes/light.js
@@ -1,49 +1,13 @@
-import * as importedTools from "./tools.js";
-import positronScheme from "./positron.js";
-
-const CLARITYFACTOR = 4.7;
-
-
-function lightScheme(string) {
-    const table = importedTools.cutRgbString(string);
-    if (table[1] != null) {
-        let newColor = importedTools.max - importedTools.clamp(table[0] / CLARITYFACTOR);
-        return importedTools.giveNewColorString(table, newColor, newColor, newColor);
+import style from './grayscale.js';
+import {Color} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.grayscale().lighten(0.1).toString();
+        return acc;
     }
-    return null;
-}
-
-export default Object.entries(positronScheme).reduce((acc, [key, value]) => {
-    acc[key] = lightScheme(value);
-    return {
-        ...acc,
-        attractionStyleWaterSlideLineColor: 'rgb(175,175,175)',
-        landuseBackgroundBasinFillColor: 'rgb(175,175,175)',
-        leisureBackgroundSwimmingPoolFillColor: 'rgb(175,175,175)',
-        leisureOverlaySwimmingPoolFillOutlineColor: 'rgb(175,175,175)',
-        naturalBackgroundWaterFillColor: 'rgb(175,175,175)',
-        naturalOverlayLakeFillColor: 'rgb(175,175,175)',
-        naturalWaterFillColor: 'rgb(175,175,175)',
-        oceanWaterFillColor: 'rgb(175,175,175)',
-        pointCountryLabelCountryTextColor: 'rgb(90, 56, 90)',
-        pointCountryLabelPaintTextHaloColor: 'rgba(255, 255, 255, 0.8)',
-        pointIconWaterfallIconColor: 'rgb(175,175,175)',
-        pointIconWaterfallTextColor: 'rgb(175,175,175)',
-        pointLabelCityFilterOneLabelColor: 'rgb(100, 100, 100)',
-        pointLabelCityFilterTwoLabelColor: 'rgb(50, 50, 50)',
-        pointLabelCityLabelColor: 'rgb(25, 25, 25)',
-        pointLabelLocalityLabelColor: 'rgb(100, 100, 100)',
-        pointLabelPaintTextHaloColor: 'rgba(255, 255, 255, 0.8)',
-        pointLabelPlaceTextColor: 'rgba(100, 100, 100, 1)',
-        pointLabelTownFilterOneLabelColor: 'rgb(100, 100, 100)',
-        pointLabelTownFilterTwoLabelColor: 'rgb(75, 75, 75)',
-        pointLabelVillageLabelColor: 'rgb(100, 100, 100)',
-        waterwayLabelTextColor: 'rgb(175,175,175)',
-        waterwayLabelTextHaloColor: 'rgb(175,175,175)',
-        waterwayLineWaterwayLineColor: 'rgb(175,175,175)',
-        waterwayTunnelCasingLineColor: 'rgb(175,175,175)',
-        waterwayTunnelLineLineColor: 'rgb(175,175,175)',
-    };
-
-
 }, {});
diff --git a/basemap/themes/positron.js b/basemap/themes/positron.js
deleted file mode 100644
index 5eb59286..00000000
--- a/basemap/themes/positron.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import style from './default.js';
-import * as importedTools from "./tools.js";
-
-export function positronScheme(string) {
-    const table = importedTools.cutRgbString(string)
-    let newColor = positronColorCalculator(table);
-
-    return importedTools.giveNewColorString(table, newColor, newColor, newColor);
-};
-
-function positronColorCalculator(table) {
-    return Math.round((parseInt(table[0]) + parseInt(table[1]) + parseInt(table[2])) / 3);
-};
-
-export default Object.entries(style).reduce((acc, [key, value]) => {
-    acc[key] = positronScheme(value);
-    return acc;
-}, {});
diff --git a/basemap/themes/protanomaly.js b/basemap/themes/protanomaly.js
new file mode 100644
index 00000000..1dd25142
--- /dev/null
+++ b/basemap/themes/protanomaly.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Protanomaly).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/protanopia.js b/basemap/themes/protanopia.js
new file mode 100644
index 00000000..bc79a85e
--- /dev/null
+++ b/basemap/themes/protanopia.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Protanopia).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/road.js b/basemap/themes/road.js
deleted file mode 100644
index a7b9e20e..00000000
--- a/basemap/themes/road.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import style from './default.js';
-import * as importedTools from "./tools.js";
-
-function roadScheme(string) {
-    const table = importedTools.cutRgbString(string);
-    let redElement = importedTools.clamp(table[0] + 50);
-    return importedTools.giveNewColorString(table, redElement, table[1], table[2]);
-}
-
-export default Object.entries(style).reduce((acc, [key, value]) => {
-    acc[key] = roadScheme(value);
-    return acc;
-}, {});
-
-
diff --git a/basemap/themes/tools.js b/basemap/themes/tools.js
deleted file mode 100644
index 0e5c815e..00000000
--- a/basemap/themes/tools.js
+++ /dev/null
@@ -1,36 +0,0 @@
-export const min = 0;
-export const max = 255;
-
-export function giveNewColorString(table, newRed, newGreen, newBlue) {
-    let opacity = table[3];
-    return `rgba(${newRed},${newGreen},${newBlue},${opacity})`;
-};
-
-export function cutRgbString(color) {
-    let rgb = color.replace(/\s*/g, '').match(/rgb\((\d*)\,(\d*)\,(\d*)\)/)
-    if (rgb != null) {
-        return [
-            parseInt(rgb[1]),
-            parseInt(rgb[2]),
-            parseInt(rgb[3]),
-            1,
-        ]
-    }
-    ;
-    let rgba = color.replace(/\s*/g, '').match(/rgba\((\d*)\,(\d*)\,(\d*)\,(.*)\)/)
-    if (rgba != null) {
-
-        return [
-            parseInt(rgba[1]),
-            parseInt(rgba[2]),
-            parseInt(rgba[3]),
-            parseFloat(rgba[4]),
-        ]
-    }
-    ;
-    return null;
-}
-
-export function clamp(value) {
-    return Math.min(Math.max(value, min), max);
-}
diff --git a/basemap/themes/transportation.js b/basemap/themes/transportation.js
new file mode 100644
index 00000000..87f8cce2
--- /dev/null
+++ b/basemap/themes/transportation.js
@@ -0,0 +1,20 @@
+import style from './default.js';
+import {Color} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else if (key.toLowerCase().includes("highway")
+        || key.toLowerCase().includes("tunnel")
+        || key.toLowerCase().includes("bridge")
+        || key.toLowerCase().includes("rail")
+        || key.toLowerCase().includes("ferry")) {
+        acc[key] = color.darken(0.2).toString();
+        return acc;
+    } else {
+        acc[key] = color.grayscale().lighten(0.1).toString();
+        return acc;
+    }
+}, {});
\ No newline at end of file
diff --git a/basemap/themes/tritanomaly.js b/basemap/themes/tritanomaly.js
new file mode 100644
index 00000000..ecbb8769
--- /dev/null
+++ b/basemap/themes/tritanomaly.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Tritanomaly).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/themes/tritanopia.js b/basemap/themes/tritanopia.js
new file mode 100644
index 00000000..b1429910
--- /dev/null
+++ b/basemap/themes/tritanopia.js
@@ -0,0 +1,13 @@
+import style from './default.js';
+import {Color, ColorBlind} from "../utils/color.js";
+
+export default Object.entries(style).reduce((acc, [key, value]) => {
+    let color = Color.fromString(value);
+    if (color == null) {
+        acc[key] = value;
+        return acc;
+    } else {
+        acc[key] = color.colorblind(ColorBlind.Tritanopia).toString();
+        return acc;
+    }
+}, {});
diff --git a/basemap/utils/color.js b/basemap/utils/color.js
index 298a6262..2a04ecf2 100644
--- a/basemap/utils/color.js
+++ b/basemap/utils/color.js
@@ -9,56 +9,312 @@
  or implied. See the License for the specific language governing permissions and limitations under
  the License.
  **/
-export function lighten(color, percent) {
-    let {r, g, b, a} = readColor(color);
-    return writeColor({
-        r: lightenValue(r, percent),
-        g: lightenValue(g, percent),
-        b: lightenValue(b, percent),
-        a: a,
-    })
-}
 
-export function darken(color, percent) {
-    let {r, g, b, a} = readColor(color);
-    return writeColor({
-        r: darkenValue(r, percent),
-        g: darkenValue(g, percent),
-        b: darkenValue(b, percent),
-        a: a,
-    })
-}
+class Color {
+
+    constructor() {
+
+    }
+
+    static fromString(color) {
+        let rgb = RGB.fromString(color);
+        if (rgb != null) {
+            return rgb;
+        }
+        let hsl = HSL.fromString(color);
+        if (hsl != null) {
+            return hsl;
+        }
+        return null;
+    }
+
+    toRGB() {
+        throw new Error('Abstract method');
+    }
+
+    toHSL() {
+        throw new Error('Abstract method');
+    }
+
+    grayscale() {
+        let rgb = this.toRGB();
+        let gray = Math.round(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b);
+        return new RGB(gray, gray, gray, rgb.a);
+    }
 
-function lightenValue(value, percent) {
-    return Math.round(value - value * percent);
+    lighten(amount) {
+        let hsl = this.toHSL();
+        hsl.l += amount;
+        hsl.l = Math.min(hsl.l, 1);
+        return hsl.toRGB();
+    }
+
+    darken(amount) {
+        let hsl = this.toHSL();
+        hsl.l -= amount;
+        hsl.l = Math.max(hsl.l, 0);
+        return hsl.toRGB();
+    }
+
+    saturate(amount) {
+        let hsl = this.toHSL();
+        hsl.s += amount;
+        hsl.s = Math.min(hsl.s, 1);
+        return hsl.toRGB();
+    }
+
+    desaturate(amount) {
+        let hsl = this.toHSL();
+        hsl.s -= amount;
+        hsl.s = Math.max(hsl.s, 0);
+        return hsl.toRGB();
+    }
+
+    fade(amount) {
+        let rgb = this.toRGB();
+        rgb.a -= amount;
+        rgb.a = Math.max(rgb.a, 0);
+        return rgb;
+    }
+
+    opacify(amount) {
+        let rgb = this.toRGB();
+        rgb.a += amount;
+        rgb.a = Math.min(rgb.a, 1);
+        return rgb;
+    }
+
+    rotate(degrees) {
+        let hsl = this.toHSL();
+        hsl.h = (hsl.h + degrees) % 360;
+        return hsl.toRGB();
+    }
+
+    invert() {
+        let rgb = this.toRGB();
+        rgb.r = 255 - rgb.r;
+        rgb.g = 255 - rgb.g;
+        rgb.b = 255 - rgb.b;
+        return rgb;
+    }
+
+    contrast(factor) {
+        factor = (1 + factor) ** 2;
+        let rgb = this.toRGB();
+        let r = ((rgb.r / 255.0 - 0.5) * factor + 0.5) * 255.0;
+        let g = ((rgb.g / 255.0 - 0.5) * factor + 0.5) * 255.0;
+        let b = ((rgb.b / 255.0 - 0.5) * factor + 0.5) * 255.0;
+        r = Math.max(0, Math.min(255, r));
+        g = Math.max(0, Math.min(255, g));
+        b = Math.max(0, Math.min(255, b));
+        return new RGB(r, g, b, rgb.a);
+    }
+
+    colorblind(type) {
+        return type(this.toRGB());
+    }
+
+    toString() {
+        throw new Error('Abstract method');
+    }
 }
 
-function darkenValue(value, percent) {
-    return Math.round(value + (255 - value) * percent);
+class RGB extends Color {
+
+    constructor(r, g, b, a = 1) {
+        super();
+        this.r = r;
+        this.g = g;
+        this.b = b;
+        this.a = a;
+    }
+
+    static fromString(color) {
+        let rgb = color.replace(/\s*/g, '').match(/rgb\((\d*)\,(\d*)\,(\d*)\)/)
+        if (rgb != null) {
+            return new RGB(parseInt(rgb[1]), parseInt(rgb[2]), parseInt(rgb[3]), 1)
+        }
+        let rgba = color.replace(/\s*/g, '').match(/rgba\((\d*)\,(\d*)\,(\d*)\,(.*)\)/)
+        if (rgba != null) {
+            return new RGB(parseInt(rgba[1]), parseInt(rgba[2]), parseInt(rgba[3]), parseFloat(rgba[4]))
+        }
+        return null
+    }
+
+    toRGB() {
+        return this;
+    }
+
+    toHSL() {
+        let r = this.r / 255;
+        let g = this.g / 255;
+        let b = this.b / 255;
+        let max = Math.max(r, g, b);
+        let min = Math.min(r, g, b);
+        let h, s, l = (max + min) / 2;
+        if (max == min) {
+            h = s = 0;
+        } else {
+            let d = max - min;
+            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+            switch (max) {
+                case r:
+                    h = (g - b) / d + (g < b ? 6 : 0)
+                    break;
+                case g:
+                    h = (b - r) / d + 2
+                    break;
+                case b:
+                    h = (r - g) / d + 4
+                    break;
+            }
+            h /= 6;
+        }
+        return new HSL(h, s, l, this.a);
+    }
+
+    toString() {
+        if (this.a == 1) {
+            return `rgb(${this.r},${this.g},${this.b})`;
+        } else {
+            return `rgba(${this.r},${this.g},${this.b},${this.a})`;
+        }
+    }
 }
 
-export function readColor(color) {
-    let rgb = color.replace(/\s*/g, '').match(/rgb\((\d*)\,(\d*)\,(\d*)\)/)
-    if (rgb != null) {
-        return {
-            r: parseInt(rgb[1]),
-            g: parseInt(rgb[2]),
-            b: parseInt(rgb[3]),
-            a: 1,
+class HSL extends Color {
+
+    constructor(h, s, l, a = 1) {
+        super();
+        this.h = h;
+        this.s = s;
+        this.l = l;
+        this.a = a;
+    }
+
+    static fromString(color) {
+        let hsl = color.replace(/\s*/g, '').match(/hsl\((\d*)\,(\d*)\,(\d*)\)/)
+        if (hsl != null) {
+            return new HSL(parseInt(hsl[1]), parseInt(hsl[2]), parseInt(hsl[3]), 1)
+        }
+        let hsla = color.replace(/\s*/g, '').match(/hsla\((\d*)\,(\d*)\,(\d*)\,(.*)\)/)
+        if (hsla != null) {
+            return new HSL(parseInt(hsla[1]), parseInt(hsla[2]), parseInt(hsla[3]), parseFloat(hsla[4]))
         }
+        return null
     }
-    let rgba = color.replace(/\s*/g, '').match(/rgba\((\d*)\,(\d*)\,(\d*)\,(.*)\)/)
-    if (rgba != null) {
-        return {
-            r: parseInt(rgba[1]),
-            g: parseInt(rgba[2]),
-            b: parseInt(rgba[3]),
-            a: parseFloat(rgba[4]),
+
+    toRGB() {
+        let r, g, b;
+        if (this.s == 0) {
+            r = g = b = this.l;
+        } else {
+            let hue2rgb = function hue2rgb(p, q, t) {
+                if (t < 0) {
+                    t += 1;
+                }
+                if (t > 1) {
+                    t -= 1;
+                }
+                if (t < 1 / 6) {
+                    return p + (q - p) * 6 * t;
+                }
+                if (t < 1 / 2) {
+                    return q;
+                }
+                if (t < 2 / 3) {
+                    return p + (q - p) * (2 / 3 - t) * 6;
+                }
+                return p;
+            }
+            let q = this.l < 0.5 ? this.l * (1 + this.s) : this.l + this.s - this.l * this.s;
+            let p = 2 * this.l - q;
+            r = hue2rgb(p, q, this.h + 1 / 3);
+            g = hue2rgb(p, q, this.h);
+            b = hue2rgb(p, q, this.h - 1 / 3);
         }
+        return new RGB(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), this.a);
+    }
+
+    toHSL() {
+        return this;
     }
-    return null
+
+    toString() {
+        if (this.a == 1) {
+            return `hsl(${this.h},${this.s},${this.l})`;
+        } else {
+            return `hsla(${this.h},${this.s},${this.l},${this.a})`;
+        }
+    }
+}
+
+function Achromatopsia(rgb) {
+    let r = rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
+    let g = rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
+    let b = rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
+    return new RGB(r, g, b, rgb.a);
+}
+
+function Achromatomaly(rgb) {
+    let r = rgb.r * 0.618 + rgb.g * 0.320 + rgb.b * 0.062;
+    let g = rgb.r * 0.163 + rgb.g * 0.775 + rgb.b * 0.062;
+    let b = rgb.r * 0.163 + rgb.g * 0.320 + rgb.b * 0.516;
+    return new RGB(r, g, b, rgb.a);
 }
 
-function writeColor(color) {
-    return `rgba(${color.r},${color.g},${color.b},${color.a})`;
+function Deuteranopia(rgb) {
+    let r = rgb.r * 0.625 + rgb.g * 0.375 + rgb.b * 0.0;
+    let g = rgb.r * 0.7 + rgb.g * 0.3 + rgb.b * 0.0;
+    let b = rgb.r * 0.0 + rgb.g * 0.3 + rgb.b * 0.7;
+    return new RGB(r, g, b, rgb.a);
 }
+
+function Deuteranomaly(rgb) {
+    let r = rgb.r * 0.8 + rgb.g * 0.2 + rgb.b * 0.0;
+    let g = rgb.r * 0.258 + rgb.g * 0.742 + rgb.b * 0.0;
+    let b = rgb.r * 0.0 + rgb.g * 0.142 + rgb.b * 0.858;
+    return new RGB(r, g, b, rgb.a);
+}
+
+function Protanopia(rgb) {
+    let r = rgb.r * 0.567 + rgb.g * 0.433 + rgb.b * 0.0;
+    let g = rgb.r * 0.558 + rgb.g * 0.442 + rgb.b * 0.0;
+    let b = rgb.r * 0.0 + rgb.g * 0.242 + rgb.b * 0.758;
+    return new RGB(r, g, b, rgb.a);
+}
+
+function Protanomaly(rgb) {
+    let r = rgb.r * 0.817 + rgb.g * 0.183 + rgb.b * 0.0;
+    let g = rgb.r * 0.333 + rgb.g * 0.667 + rgb.b * 0.0;
+    let b = rgb.r * 0.0 + rgb.g * 0.125 + rgb.b * 0.875;
+    return new RGB(r, g, b, rgb.a);
+}
+
+
+function Tritanopia(rgb) {
+    let r = rgb.r * 0.95 + rgb.g * 0.05 + rgb.b * 0.0;
+    let g = rgb.r * 0.0 + rgb.g * 0.433 + rgb.b * 0.567;
+    let b = rgb.r * 0.0 + rgb.g * 0.475 + rgb.b * 0.525;
+    return new RGB(r, g, b, rgb.a);
+}
+
+function Tritanomaly(rgb) {
+    let r = rgb.r * 0.967 + rgb.g * 0.033 + rgb.b * 0.0;
+    let g = rgb.r * 0.0 + rgb.g * 0.733 + rgb.b * 0.267;
+    let b = rgb.r * 0.0 + rgb.g * 0.183 + rgb.b * 0.817;
+    return new RGB(r, g, b, rgb.a);
+}
+
+const ColorBlind = {
+    Achromatopsia: Achromatopsia,
+    Achromatomaly: Achromatomaly,
+    Deuteranopia: Deuteranopia,
+    Deuteranomaly: Deuteranomaly,
+    Protanopia: Protanopia,
+    Protanomaly: Protanomaly,
+    Tritanopia: Tritanopia,
+    Tritanomaly: Tritanomaly
+}
+
+export {Color, RGB, HSL, ColorBlind};