From 26b8ef8af93cfe7a962d48f5c9e7a7bffdfa2937 Mon Sep 17 00:00:00 2001
From: Constantin Wartenburger <ginnythecat@lelux.net>
Date: Mon, 5 Oct 2020 17:28:05 +0200
Subject: [PATCH] Changed rainbow algorithm

---
 CHANGES.md                                    |  1 +
 .../composer/rainbow/RainbowGenerator.kt      | 83 ++++++++++---------
 .../composer/rainbow/RainbowGeneratorTest.kt  | 78 ++++++++---------
 3 files changed, 83 insertions(+), 79 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 4a158086a2..f0bae1aa32 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,6 +4,7 @@ Changes in Element 1.0.9 (2020-XX-XX)
 Features ✨:
  - Search messages in a room - phase 1 (#2110)
  - Hide encrypted history (before user is invited). Can be shown if wanted in developer settings
+ - Changed rainbow algorithm
 
 Improvements 🙌:
  - Wording differentiation for direct rooms (#2176)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt
index 7bed9f8e64..7a64d437c5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGenerator.kt
@@ -18,8 +18,10 @@ package im.vector.app.features.home.room.detail.composer.rainbow
 
 import im.vector.app.core.utils.splitEmoji
 import javax.inject.Inject
-import kotlin.math.abs
+import kotlin.math.cos
+import kotlin.math.pow
 import kotlin.math.roundToInt
+import kotlin.math.sin
 
 /**
  * Inspired from React-Sdk
@@ -29,7 +31,7 @@ class RainbowGenerator @Inject constructor() {
 
     fun generate(text: String): String {
         val split = text.splitEmoji()
-        val frequency = 360f / split.size
+        val frequency = 360.0 / split.size
 
         return split
                 .mapIndexed { idx, letter ->
@@ -37,53 +39,54 @@ class RainbowGenerator @Inject constructor() {
                     if (letter == " ") {
                         "$letter"
                     } else {
-                        val dashColor = hueToRGB(idx * frequency, 1.0f, 0.5f).toDashColor()
+                        val (a, b) = generateAB(idx * frequency, 1f)
+                        val dashColor = labToRGB(75, a, b).toDashColor()
                         "<font color=\"$dashColor\">$letter</font>"
                     }
                 }
                 .joinToString(separator = "")
     }
 
-    private fun hueToRGB(h: Float, s: Float, l: Float): RgbColor {
-        val c = s * (1 - abs(2 * l - 1))
-        val x = c * (1 - abs((h / 60) % 2 - 1))
-        val m = l - c / 2
+    private fun generateAB(hue: Double, chroma: Float): Pair<Double, Double> {
+        val radians = Math.toRadians(hue)
 
-        var r = 0f
-        var g = 0f
-        var b = 0f
+        val a = chroma * 127 * cos(radians)
+        val b = chroma * 127 * sin(radians)
 
-        when {
-            h < 60f  -> {
-                r = c
-                g = x
-            }
-            h < 120f -> {
-                r = x
-                g = c
-            }
-            h < 180f -> {
-                g = c
-                b = x
-            }
-            h < 240f -> {
-                g = x
-                b = c
-            }
-            h < 300f -> {
-                r = x
-                b = c
-            }
-            else     -> {
-                r = c
-                b = x
-            }
+        return Pair(a, b)
+    }
+
+    private fun labToRGB(l: Int, a: Double, b: Double): RgbColor {
+        var y = (l + 16) / 116.0
+        val x = adjustXYZ(y + a / 500) * 0.9505
+        val z = adjustXYZ(y - b / 200) * 1.0890
+
+        y = adjustXYZ(y)
+
+        val red = 3.24096994 * x - 1.53738318 * y - 0.49861076 * z
+        val green = -0.96924364 * x + 1.8759675 * y + 0.04155506 * z
+        val blue = 0.05563008 * x - 0.20397696 * y + 1.05697151 * z
+
+        return RgbColor(adjustRGB(red), adjustRGB(green), adjustRGB(blue))
+    }
+
+    private fun adjustXYZ(value: Double): Double {
+        if (value > 0.2069) {
+            return value.pow(3)
         }
+        return 0.1284 * value - 0.01771
+    }
 
-        return RgbColor(
-                ((r + m) * 255).roundToInt(),
-                ((g + m) * 255).roundToInt(),
-                ((b + m) * 255).roundToInt()
-        )
+    private fun gammaCorrection(value: Double): Double {
+        if (value <= 0.0031308) {
+            return 12.92 * value
+        }
+        return 1.055 * value.pow(1 / 2.4) - 0.055
+    }
+
+    private fun adjustRGB(value: Double): Int {
+        return (gammaCorrection(value)
+                .coerceIn(0.0, 1.0) * 255)
+                .roundToInt()
     }
 }
diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt
index 0e46d67860..20b041fd1e 100644
--- a/vector/src/test/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt
+++ b/vector/src/test/java/im/vector/app/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt
@@ -32,14 +32,14 @@ class RainbowGeneratorTest {
 
     @Test
     fun testAscii1() {
-        assertEquals("""<font color="#ff0000">a</font>""", rainbowGenerator.generate("a"))
+        assertEquals("""<font color="#ff00be">a</font>""", rainbowGenerator.generate("a"))
     }
 
     @Test
     fun testAscii2() {
         val expected = """
-            <font color="#ff0000">a</font>
-            <font color="#00ffff">b</font>
+            <font color="#ff00be">a</font>
+            <font color="#00e6b6">b</font>
         """.trimIndentOneLine()
 
         assertEquals(expected, rainbowGenerator.generate("ab"))
@@ -48,24 +48,24 @@ class RainbowGeneratorTest {
     @Test
     fun testAscii3() {
         val expected = """
-            <font color="#ff0000">T</font>
-            <font color="#ff5500">h</font>
-            <font color="#ffaa00">i</font>
-            <font color="#ffff00">s</font>
+            <font color="#ff00be">T</font>
+            <font color="#ff0072">h</font>
+            <font color="#ff3b1d">i</font>
+            <font color="#ff7e00">s</font>
              
-            <font color="#55ff00">i</font>
-            <font color="#00ff00">s</font>
+            <font color="#bdc100">i</font>
+            <font color="#64d200">s</font>
              
-            <font color="#00ffaa">a</font>
+            <font color="#00e261">a</font>
              
-            <font color="#00aaff">r</font>
-            <font color="#0055ff">a</font>
-            <font color="#0000ff">i</font>
-            <font color="#5500ff">n</font>
-            <font color="#aa00ff">b</font>
-            <font color="#ff00ff">o</font>
-            <font color="#ff00aa">w</font>
-            <font color="#ff0055">!</font>
+            <font color="#00e7ff">r</font>
+            <font color="#00e6ff">a</font>
+            <font color="#00e1ff">i</font>
+            <font color="#00d4ff">n</font>
+            <font color="#00bdff">b</font>
+            <font color="#9598ff">o</font>
+            <font color="#ff60ff">w</font>
+            <font color="#ff00ff">!</font>
         """.trimIndentOneLine()
 
         assertEquals(expected, rainbowGenerator.generate("This is a rainbow!"))
@@ -73,19 +73,19 @@ class RainbowGeneratorTest {
 
     @Test
     fun testEmoji1() {
-        assertEquals("""<font color="#ff0000">🤞</font>""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🤞
+        assertEquals("""<font color="#ff00be">🤞</font>""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🤞
     }
 
     @Test
     fun testEmoji2() {
-        assertEquals("""<font color="#ff0000">🤞</font>""", rainbowGenerator.generate("🤞"))
+        assertEquals("""<font color="#ff00be">🤞</font>""", rainbowGenerator.generate("🤞"))
     }
 
     @Test
     fun testEmoji3() {
         val expected = """
-            <font color="#ff0000">🤞</font>
-            <font color="#00ffff">🙂</font>
+            <font color="#ff00be">🤞</font>
+            <font color="#00e6b6">🙂</font>
         """.trimIndentOneLine()
 
         assertEquals(expected, rainbowGenerator.generate("🤞🙂"))
@@ -94,20 +94,20 @@ class RainbowGeneratorTest {
     @Test
     fun testEmojiMix1() {
         val expected = """
-            <font color="#ff0000">H</font>
-            <font color="#ff6d00">e</font>
-            <font color="#ffdb00">l</font>
-            <font color="#b6ff00">l</font>
-            <font color="#49ff00">o</font>
+            <font color="#ff00be">H</font>
+            <font color="#ff005d">e</font>
+            <font color="#ff6700">l</font>
+            <font color="#ffa100">l</font>
+            <font color="#b2c400">o</font>
              
-            <font color="#00ff92">🤞</font>
+            <font color="#00e147">🤞</font>
              
-            <font color="#0092ff">w</font>
-            <font color="#0024ff">o</font>
-            <font color="#4900ff">r</font>
-            <font color="#b600ff">l</font>
-            <font color="#ff00db">d</font>
-            <font color="#ff006d">!</font>
+            <font color="#00e7ff">w</font>
+            <font color="#00e4ff">o</font>
+            <font color="#00d6ff">r</font>
+            <font color="#00b9ff">l</font>
+            <font color="#da83ff">d</font>
+            <font color="#ff03ff">!</font>
         """.trimIndentOneLine()
 
         assertEquals(expected, rainbowGenerator.generate("Hello 🤞 world!"))
@@ -116,8 +116,8 @@ class RainbowGeneratorTest {
     @Test
     fun testEmojiMix2() {
         val expected = """
-            <font color="#ff0000">a</font>
-            <font color="#00ffff">🤞</font>
+            <font color="#ff00be">a</font>
+            <font color="#00e6b6">🤞</font>
         """.trimIndentOneLine()
 
         assertEquals(expected, rainbowGenerator.generate("a🤞"))
@@ -126,8 +126,8 @@ class RainbowGeneratorTest {
     @Test
     fun testEmojiMix3() {
         val expected = """
-            <font color="#ff0000">🤞</font>
-            <font color="#00ffff">a</font>
+            <font color="#ff00be">🤞</font>
+            <font color="#00e6b6">a</font>
         """.trimIndentOneLine()
 
         assertEquals(expected, rainbowGenerator.generate("🤞a"))
@@ -135,6 +135,6 @@ class RainbowGeneratorTest {
 
     @Test
     fun testError1() {
-        assertEquals("<font color=\"#ff0000\">\uD83E</font>", rainbowGenerator.generate("\uD83E"))
+        assertEquals("<font color=\"#ff00be\">\uD83E</font>", rainbowGenerator.generate("\uD83E"))
     }
 }