{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Modèles de couleurs\n",
    "\n",
    "Marc Lorenzi\n",
    "\n",
    "30 mars 2019"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import math, cmath\n",
    "import colorsys\n",
    "import random"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.rcParams['figure.figsize'] = (8, 8)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Les modèles de couleurs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Le modèle RGB"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le modèle RGB est le modèle utilisé pour décrire les couleurs affichées sur les écrans d'ordinateur. Il est utilisé par la plupart des fonctions de tracé de graphiques dans les langages de programmation \"standard\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 C'est quoi ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dans le modèle __RGB (Red, Green, Blue)__ une couleur est codée par un triplet $(r,g,b)$ de flottants entre 0 et 1, représentant les \"quantités\" de rouge, de vert et de bleu de la couleur considérée. Il s'agit d'un modèle _additif_, c'est à dire que le triplet $(0,0,0)$ représente la couleur noire et le triplet $(1,1,1)$ représente la couleur blanche. Le rouge vif est représenté par $(1, 0, 0)$, etc. \n",
    "\n",
    "Dans le dessin ci-dessous, on représente trois disques colorés avec les couleurs primaires R, G, B. Les intersections de ces disques reçoivent la couleur obtenue en __additionnant__ les couleurs des disques : le modèle RGB est un modèle __additif__, c'est ce que l'on obtient dans la réalité avec des objets qui émettent de la lumière, comme par exemple des spots lumineux."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def distance(a, b, c, d):\n",
    "    return math.sqrt((c - a) ** 2 + (d - b) ** 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 600\n",
    "extent = [-3, 3, -3, 3]\n",
    "xmin, xmax, ymin, ymax = extent\n",
    "R = 2\n",
    "m = [[(0., 0., 0.) for i in range(N)] for j in range(N)]\n",
    "for i in range(N):\n",
    "    for j in range(N):\n",
    "        r, g, b = 0., 0., 0.\n",
    "        x = xmin + j * (xmax - xmin) / N\n",
    "        y = ymax - i * (ymax - ymin) / N\n",
    "        d = distance(x, y, math.sqrt(3)/2, -1/2) \n",
    "        if d <= R: b = 1.\n",
    "        d = distance(x, y, -math.sqrt(3)/2, -1/2) \n",
    "        if d <= R: g = 1.\n",
    "        d = distance(x, y, 0, 1) \n",
    "        if d <= R: r = 1.\n",
    "        m[i][j] = (r, g, b)\n",
    "plt.imshow(m, extent=extent)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Autre représentation équivalente, le dessin ci-dessous représente le cube $[0,1]\\times[0,1]\\times[0,1]$. Les sommets du cube sont colorés en fonction de leurs coordonnées."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot([0,-0.5],[0,-0.5],'k')\n",
    "plt.plot([-0.5,1.5],[-0.5,-0.5],'k')\n",
    "plt.plot([1.5,2],[-0.5,0],'k')\n",
    "plt.plot([2,0],[0,0],'k')\n",
    "\n",
    "plt.plot([0,-0.5],[2,1.5],'k')\n",
    "plt.plot([-0.5,1.5],[1.5,1.5],'k')\n",
    "plt.plot([1.5,2],[1.5,2],'k')\n",
    "plt.plot([2,0],[2,2],'k')\n",
    "\n",
    "plt.plot([-0.5, -0.5], [-0.5, 1.5], 'k')\n",
    "plt.plot([0, 0], [0, 2], 'k')\n",
    "plt.plot([1.5, 1.5], [-0.5, 1.5], 'k')\n",
    "plt.plot([2, 2], [0, 2], 'k')\n",
    "\n",
    "plt.plot([0],[0], 'o', color=(0,0,0), markersize=40)\n",
    "plt.plot([-0.5],[-0.5], 'o', color=(1,0,0), markersize=40)\n",
    "plt.plot([1.5],[-0.5], 'o', color=(1,1,0), markersize=40)\n",
    "plt.plot([2],[0], 'o', color=(0,1,0), markersize=40)\n",
    "\n",
    "plt.plot([0],[2], 'o', color=(0,0,1), markersize=40)\n",
    "plt.plot([-0.5],[1.5], 'o', color=(1,0,1), markersize=40)\n",
    "plt.plot([1.5],[1.5], 'o', color=(1,1,1), markersize=40)\n",
    "plt.plot([2],[2], 'o', color=(0,1,1), markersize=40)\n",
    "\n",
    "plt.text(-0.4, -0.4, \"Rouge(0,0,1)\", fontdict={'fontsize':16})\n",
    "plt.text(0.1, 0.1, \"Noir(0,0,0)\", fontdict={'fontsize':16})\n",
    "plt.text(-0.4, 1.4, \"Magenta(1,0,1)\", fontdict={'fontsize':16})\n",
    "plt.text(0.1, 1.9, \"Bleu(0,0,1)\", fontdict={'fontsize':16})\n",
    "plt.text(1.4, -0.4, \"Jaune(1,1,0)\", horizontalalignment='right', fontdict={'fontsize':16})\n",
    "plt.text(1.9, 0.1, \"Vert(0,1,0)\", horizontalalignment='right', fontdict={'fontsize':16})\n",
    "plt.text(1.4, 1.4, \"Blanc(1,1,1)\", horizontalalignment='right', fontdict={'fontsize':16})\n",
    "plt.text(1.9, 1.9, \"Cyan(0,1,1)\", horizontalalignment='right', fontdict={'fontsize':16})\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : Imaginez que vous voyez le cube ci-dessus dans la direction de la diagonale Blanc $\\to$ Noir. Les 6 sommets restants forment un hexagone. Dans le sens trigonométrique, vous voyez les couleurs Rouge, Jaune, Vert, Cyan, Bleu, Magenta. Bref, en gros les couleurs de l'arc en ciel. Nous y reviendrons lorsque nous parlerons du modèle HSV."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Affichons un carré coloré"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si le modèle RGB est très intuitif, il présente l'inconvénient d'être \"tridimensionnel\". Il n'est pas si évident que cela de faire un dessin dans lequels toutes les couleurs sont présentes. Le dessin ci-dessous, par exemple, affiche quelques couleurs dans un carré, mais pas toutes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 200\n",
    "m = [[0 for i in range(N)] for j in range(N)]\n",
    "for i in range(N):\n",
    "    for j in range(N):\n",
    "        m[i][j] = (i / N, j / N, 0)\n",
    "plt.imshow(m, extent=[0, 1, 1, 0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En haut à gauche, nous avons $\\frac i N$ et $\\frac j N$ petits, et donc des couleurs proches de $(0,0,0)$, c'est à dire du noir. En bas à droite, nous avons $i$ et $j$ grands, et donc des couleurs proches de $(1,1,0)$, c'est à dire du jaune. Je vous laisse commenter les couleurs aux deux derniers coins de ce carré."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 Barycentres !"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Faisons une autre tentative. Soit $(RGB)$ le triangle de sommets $R=(1,0)$, $B=(-1,0)$ et $G=(0,1)$. Tout point $P=(x,y)$ à l'intérieur de ce triangle est un barycentre de $R$, $G$ et $B$ à coefficients positifs :\n",
    "\n",
    "$$P=\\alpha R+\\beta B+\\gamma G$$\n",
    "\n",
    "où $\\alpha,\\beta,\\gamma\\ge 0$ et pas tous nuls. Comme il s'agit d'un barycentre, ces coefficients ne sont pas déterminés de façon unique. Nous pouvons par exemple imposer $\\alpha+\\beta+\\gamma=1$. On a donc\n",
    "\n",
    "$$(x,y)=\\alpha(1,0)+\\beta(-1,0)+(1-\\alpha-\\beta)(0,1)$$\n",
    "\n",
    "qui donne facilement\n",
    "\n",
    "$$\\alpha = \\frac{x+1-y}{2}\\quad{\\rm et}\\quad\\beta=\\frac{-x+1-y}{2}$$\n",
    "\n",
    "La cellule ci-dessous dessine le triangle coloré. Pour \"chaque\" point du rectangle $[-1,1]\\times[0,1]$, on calcule les coefficients $\\alpha,\\beta,\\gamma$. S'ils sont tous les trois positifs, le point est dans le triangle et on le colorie en conséquence. Pour augmenter la luminosité du résultat, on divise $\\alpha,\\beta$ et $\\gamma$ par le plus grand de ces trois nombres. Cela amène l'une des trois valeurs au maximum, 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 300\n",
    "m = [[(0, 0, 0) for i in range(N)] for j in range(N)]\n",
    "for i in range(N):\n",
    "    for j in range(N):\n",
    "        x = -1 + j * 2 / N\n",
    "        y = 1 - i  / N\n",
    "        a = (x + 1 - y) / 2\n",
    "        b = (1 - y - x) / 2\n",
    "        c = 1 - a - b\n",
    "        d = max(a, b, c)\n",
    "        if a >= 0 and b >= 0 and c >= 0:\n",
    "            m[i][j] = (a / d, c / d, b / d)\n",
    "plt.imshow(m, interpolation='bicubic', extent=[-1, 1, 0, 1], aspect='auto')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C'est un peu mieux que notre première tentative parce que nous arrivons à naviguer de façon continue entre le rouge, le vert et le bleu. Mais non, nous n'avons pas affiché toutes les couleurs possibles ! Passons à un autre modèle de couleurs, beaucoup plus intuitif à manipuler : le modèle HSV."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : Si vous tournez dans le sens trigonométrique autour du centre de gravité du triangle, vous parcourez les couleurs de l'arc en ciel."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Le modèle HSV"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1 C'est quoi ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le modèle __HSV (Hue, Separation, Value)__ est un modèle de couleurs plus intuitif à manipuler que le modèle RGB. Chaque couleur est représentée par un triplet $(h, s, v)$ où\n",
    "\n",
    "- $h$ (\"hue\", __teinte__ en français) est ce que les gens appellent communément la \"couleur\". $h=0$ correspond au rouge. Lorsque $h$ augmente, on passe par les couleurs de l'arc en ciel : rouge, orange, jaune, vert, bleu, violet ... puis, lorsque $h$ tend vers 1, on repasse du violet au rouge. Nous avons donc un cycle de couleurs. $h$ est en gros le plus grand des trois paramètres $r,g,b$ du modèle RGB, ou une combinaison des deux plus grands. Il nous faudra bien entendu être plus précis dans le suite.\n",
    "\n",
    "- $s$ est la __saturation__. plus $s$ est petit, plus les couleurs sont \"fades\". Dans le système RGB, augmenter $s$ consiste à augmenter le minimum des trois valeurs $r,g,b$.\n",
    "\n",
    "- $v$ (\"value\", \"brightness\", \"luminance\" ou \"brillance\" en français) est __l'intensité__ de la couleur. Dans le système RGB, augmenter $v$ consiste à augmenter simultanément $r,g,b$ en gardant leurs proportions respectives constantes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 Un disque coloré"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La cellule ci-dessous colorie les pixels d'un disque avec :\n",
    "\n",
    "- Une intensité croissante du centre vers le bord.\n",
    "- Une teinte vraiant comme l'angle polaire.\n",
    "\n",
    "`matplotlib` atend des couleurs dans le modèle RGB. Utilisons pour l'instant le module `colorsys` pour faire des conversions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 400\n",
    "m = [[(0, 0, 0) for i in range(N)] for j in range(N)]\n",
    "for i in range(N):\n",
    "    for j in range(N):\n",
    "        x = -1 + j * 2 / N\n",
    "        y = 1 - i * 2 / N\n",
    "        z = x + 1j * y\n",
    "        t = cmath.phase(z) / (2 * math.pi)\n",
    "        if t < 0: t = t + 1\n",
    "        r = abs(z)\n",
    "        if r <= 1: m[i][j] = colorsys.hsv_to_rgb(t, 1, r)\n",
    "plt.imshow(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et non, nous n'avons toujours pas affiché __toutes__ les couleurs : comme nous avons pris $s=1$ nous avons affiché toutes les couleurs de saturation maximale."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Exercice__ : Modifiez la cellule ci-dessus pour voir l'effet du paramètre $s$. Mettez-le à $0.5$ au lieu de $1$, par exemple. Qu'obtenez-vous pour $s=0$ ? Eh oui, des couleurs vraiment très fades :-)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Conversions entre HSV et RGB"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit $(r,g,b)$ un triplet représentant une couleur dans le modèle RGB. Soit $(h,s,v)$ sa représentation dans le modèle HSV. Comment passer d'une représentation à l'autre ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1 RGB $\\to$ HSV"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Commençons par la conversion RGB $\\to$ HSV. Les explications que j'ai données ci-dessus du modèle HSV étaient assez vagues. Ici, nous allons être plus précis. Notre but est d'écrire une fonction `rgb_to_hsv` qui renvoie exactement les mêmes valeurs que la fonction correspondante du module `colorsys`. Pour cela, il faut évidemment se plonger dans la documentation, on ne peut pas __deviner__ une convention ! Que nous dit-elle, cette doc ? On a tout d'abord \n",
    "\n",
    "$$v = \\max(r, g, b)$$\n",
    "\n",
    "Posons ensuite \n",
    "\n",
    "$$\\Delta=\\max(r,g,b)-\\min(r,g,b)$$\n",
    "\n",
    "Si $v=0$, alors $r=g=b=0$ : nous avons affaire à du noir. On a donc $s=0$. Sinon, on pose \n",
    "\n",
    "$$s=\\frac \\Delta v$$\n",
    "\n",
    "Dans la suite, on suppose $v>0$. Passons au calcul délicat, celui de la teinte $h$. Si la saturation $s$ est nulle, on a affaire à une teinte de gris. On prend $h=0$. Sinon, $s>0$ et donc, aussi, $D>0$. La documentation du modèle HSV nous dit que trois cas se présentent :\n",
    "\n",
    "- Cas 1, $r=v$. On pose $h_0=\\frac{g-b} {\\Delta}$. Ici, on a $-1\\le h_0 < 0$ si $g < b$ et $0\\le h_0 < 1$ si $b \\le g$.\n",
    "- Cas 2, $g=v$. On pose $h_0=2 + \\frac{b-r} {\\Delta}$. Dans ce cas $1\\le h_0 < 3$.\n",
    "- Cas 3, $b=v$. On pose $h_0=4 + \\frac{r-g} {\\Delta}$. On a dans ce cas $3\\le h_0 < 5$.\n",
    "\n",
    "Si, après calcul, $h_0<0$, on remplace $h_0$ par $h_0+6$. Enfin, on pose \n",
    "\n",
    "$$h=\\frac {h_0} 6$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Petit résumé utile pour la suite :\n",
    "\n",
    "- Si $b\\le g\\le r$ alors $0\\le h_0< 1$.\n",
    "- Si $b< r\\le g$ alors $1\\le h_0< 2$.\n",
    "- Si $r\\le b \\le g$ alors $2\\le h_0< 3$.\n",
    "- Si $r < g \\le b$ alors $3\\le h_0< 4$.\n",
    "- Si $g \\le r \\le b$ alors $4\\le h_0< 5$.\n",
    "- Si $g< b\\le r$ alors $5\\le h_0< 6$.\n",
    "\n",
    "__Remarque__ : Peut-on avoir $h=1$ ? Apparemment non puisque $0\\le h_0<6$, et donc $0\\le h< 1$. Mais je préfère attendre la fin du prochain paragraphe pour développer un peu à ce sujet. Nous y verrons que $h=1$ ne pose aucun problème."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def rgb_to_hsv(r, g, b):\n",
    "    v = max(r, g, b)\n",
    "    D = v - min(r, g, b)\n",
    "    if v == 0: s = 0\n",
    "    else: s = D / v\n",
    "    if s == 0: return (0, s, v)\n",
    "    else:\n",
    "        if r == v: h0 = (g - b) / D\n",
    "        elif g == v: h0 = 2 + (b - r) / D\n",
    "        else: h0 = 4 + (r - g) / D\n",
    "        if h0 < 0: h0 = h0 + 6\n",
    "        return (h0 / 6, s, v)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Testons avec des triplets $(r, g, b)$ aléatoires. Comparons avec la fonction du module `colorsys`, nous devrions trouver des résultats identiques (aux erreurs d'arrondi près)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r, g, b = (random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1))\n",
    "print(rgb_to_hsv(r, g, b))\n",
    "print(colorsys.rgb_to_hsv(r, g, b))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tout a l'air de fonctionner. Écrivons maintenant la fonction réciproque !"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 HSV $\\to$ RGB"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La conversion HSV $\\to$ RGB est un peu plus délicate que la conversion inverse. Supposons dans ce qui suit $h,s,v$ connus et cherchons $r,g,b$.\n",
    "\n",
    "Si $s=0$, c'est facile, on a affaire à une teinte de gris $r=g=b=v$. Supposons dorénavant $v>0$. Reprenant les calculs du paragraphe précédent, on voit que\n",
    "\n",
    "$$\\Delta = vs$$\n",
    "\n",
    "Rappelez-vous que $\\Delta=\\max(r,g,b)-\\min(r,g,b)$.\n",
    "\n",
    "Soit $h_0 = 6h$. Il y a 6 cas à considérer.\n",
    "\n",
    "- Cas 0 : $0\\le h_0 < 1$. On a donc $b \\le g\\le r=v$, $g-b=h_0\\Delta$ et $\\Delta=v-b$\n",
    "- Cas 1 : $1\\le h_0 < 2$. On a donc $b< r\\le g=v$, $b-r=(h_0-2)\\Delta$ et $\\Delta=v-b$.\n",
    "- Cas 2 : $2\\le h_0 < 3$. On a donc $r\\le b\\le g=v$, $b-r=(h_0-2)\\Delta$ et $\\Delta=v-r$.\n",
    "- Cas 3 : $3\\le h_0 < 4$. On a donc $r< g\\le b=v$, $r-g=(h_0-4)\\Delta$ et $\\Delta=v-r$.\n",
    "- Cas 4 : $4\\le h_0 < 5$. On a donc $g\\le r\\le b=v$, $r-g=(h_0-4)\\Delta$ et $\\Delta=v-g$.\n",
    "- Cas 5 : $5\\le h_0 < 6$. On a donc $g< b\\le r=v$, $g-b=(h_0-6)\\Delta$ et $\\Delta=v-g$.\n",
    "\n",
    "On en déduit facilement les valeurs de $r, g, b$ dans chacun des cas (exercice !)\n",
    "\n",
    "- Cas 0 : $(r,g,b)=(v,v+(h_0-1)\\Delta,v-\\Delta)$.\n",
    "- Cas 1 : $(r,g,b)=(v-(h_0-1)\\Delta,v,v-\\Delta)$.\n",
    "- Cas 2 : $(r,g,b)=(v-\\Delta,v,v + (h_0-3)\\Delta)$.\n",
    "- Cas 3 : $(r,g,b)=(v-\\Delta,v-(h_0-3)\\Delta,v)$.\n",
    "- Cas 4 : $(r,g,b)=(v+(h_0-5)\\Delta,v-\\Delta,v)$.\n",
    "- Cas 5 : $(r,g,b)=(v,v-\\Delta, v - (h_0 - 5)\\Delta)$.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "D'où la fonction `hsv_to_rgb`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def hsv_to_rgb(h, s, v):\n",
    "    if s == 0:\n",
    "        return (v, v, v)\n",
    "    else:\n",
    "        D = v * s\n",
    "        h0 = 6 * h\n",
    "        if h0 < 1: return(v, v + (h0 - 1) * D, v - D)\n",
    "        elif h0 < 2: return (v - (h0 - 1) * D, v, v - D)\n",
    "        elif h0 < 3: return (v - D, v, v + (h0 - 3) * D)\n",
    "        elif h0 < 4: return (v - D, v - (h0 - 3) * D, v) \n",
    "        elif h0 < 5: return (v + (h0 - 5) * D, v - D, v)\n",
    "        else: return (v, v - D, v - (h0 - 5) * D)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Testons tout cela."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "h, s, v = (random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1))\n",
    "print(hsv_to_rgb(h, s, v))\n",
    "print(colorsys.hsv_to_rgb(h, s, v))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Autre test possible, composons `rgb_to_hsv` et `hsv_to_rgb`. On devrait trouver l'identité, aux erreurs d'arrondi près."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "h, s, v = (random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1))\n",
    "r, g, b = hsv_to_rgb(h, s, v)\n",
    "h1, s1, v1 = rgb_to_hsv(r, g, b)\n",
    "print(h, s, v)\n",
    "print(h1, s1, v1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Test ultime, redessinons le disque des couleurs que nous avions tracé un peu plus haut mais en utilisant notre propre fonction `hsv_to_rgb`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 300\n",
    "m = [[(0, 0, 0) for i in range(N)] for j in range(N)]\n",
    "for i in range(N):\n",
    "    for j in range(N):\n",
    "        x = -1.2 + j * 2.4 / N\n",
    "        y = 1.2 - i * 2.4 / N\n",
    "        z = x + 1j * y\n",
    "        theta = cmath.phase(z)\n",
    "        if theta < 0: theta = theta + 2 * math.pi\n",
    "        r = abs(z)\n",
    "        if r <= 1: m[i][j] = hsv_to_rgb(theta / (2 * math.pi), 1, r)\n",
    "plt.imshow(m, extent=[-1.2, 1.2, -1.2, 1.1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3 Le cas $h=1$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nos formules de conversion RGB $\\to$ HSV créent des triplets $(h,s,v)$ tels que $0\\le h<1$. Qu'arrive t-il, juste pour voir, si on met le paramètre $h$ à 1 dans la fonction réciproque `hsv_to_rgb` ?\n",
    "\n",
    "__Proposition__ : `hsv_to_rgb(0, s, v) = hsv_to_rgb(1, s, v)`.\n",
    "\n",
    "__Démonstration__ : Regardons le code de la fonction `hsv_to_rgb`. \n",
    "\n",
    "- Si $h=1$ alors $h_0=6$ et la fonction exécute la dernière branche `else` du test. Elle renvoie donc $(v, v - \\Delta, v - (h_0 - 5)\\Delta)=(v, v-\\Delta,v-\\Delta)$.  \n",
    "\n",
    "- Si $h=0$, en revanche, on a $h_0=0$ et le premier test réussit. La fonction renvoie donc $(v, v+(h_0-1)\\Delta, v - \\Delta)=(v, v-\\Delta,v-\\Delta)$.\n",
    "\n",
    "On obtient bien la même valeur dans les deux cas. En fait, plutôt que de voir $h$ varier dans l'intervalle $[0,1[$, il vaut mieux penser à $h$ variant sur un cercle ... \n",
    "\n",
    "Petite expérience avec le module `colorsys` ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r, g, b = colorsys.hsv_to_rgb(1, 0.3, 0.6)\n",
    "print(r, g, b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "h, s, v = colorsys.rgb_to_hsv(r, g, b)\n",
    "print(h, s, v)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eh oui, les fonctions `rgb_to_hsv` et `hsv_to_rgb` ne sont réciproques l'une de l'autre que si l'on suppose $0=1$ :-).\n",
    "\n",
    "__Exercice__ : effacez `colorsys.` dans les deux cellules ci-dessus. Que font nos propres fonctions ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Les modèles CMY et CMYK"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.1 Le modèle CMY"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "L'espace de couleurs CMY (Cyan, Magenta, Yellow) est l'ensemble des couleurs réalisables avec les trois couleurs Cyan, Magenta et Jaune. Contrairement à RGB, l'idée est que ces couleurs sont obtenues à l'aide de colorants (encre, peinture) et pas en \"allumant\" des pixels sur un écran. Qu'est-ce que cela change ? Eh bien la façon dont les couleurs s'additionnent.\n",
    "\n",
    "Prenons un exemple.\n",
    "\n",
    "- Une encre de couleur Cyan absorbe la lumière rouge et réfléchit la lumière bleue et la lumière verte.\n",
    "- Une encre de couleur Jaune absorbe la lumière bleue et réfléchit la lumière rouge et la lumière verte.\n",
    "\n",
    "Si on mélange à parts égales une encre Cyan et une encre Jaune, la lumière rouge et la lumière bleure seront absorbées, et seule la lumière verte sera réfléchie. Dans ce modèle on a donc Cyan + Jaune = Vert.\n",
    "\n",
    "Dans le modèle RGB, en revanche, \"allumer un pixel jaune\" signifie en réalité allumer un pixel rouge et un pixel vert. Et \"allumer un pixel Cyan\" signifie allumer un pixel bleu et un pixel vert. Ainsi, \"allumer\" du jaune et du cyan donnerait Cyan + Jaune = Rouge + Vert + Vert + Bleu. Ce qui donnerait probablement du blanc ... enfin, là c'est un peu une discussion en l'air, parce qu'on ne peut pas additionner des triplets $(r, v, b)$. Le cube $[0,1]\\times[0,1]\\times[0,1]$ n'est pas muni d'une loi de composition interne :-).\n",
    "\n",
    "L'espace RGB est un espace où les couleurs __s'additionnent__ (plus on allume de pixels plus la couleur est claire), alors que l'espace CMY est un espace où les couleurs __se soustrayent__ (plus on mélange d'encres plus la couleur est sombre). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voici un petit dessin qui est l'équivalent de ce que nous avons fait pour RGB. La différence est qu'au départ nous avons une feuille de papier blanche. En raisonnant dans le modèle RGB, tous les pixels de la feuille ont pour valeur $(r,g,b)=(1,1,1)$. Rajouter de l'encre sur la feuille a pour effet de diminuer les composantes des triplets. Ajouter de l'encre rouge, par exemple, transforme le pixel $(1,1,1)$ en le pixel $(0,1,1)$. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 400\n",
    "extent = [-3, 3, -2.9, 3.1]\n",
    "xmin, xmax, ymin, ymax = extent\n",
    "R = 2\n",
    "m = [[(1., 1., 1.) for i in range(N)] for j in range(N)]\n",
    "for i in range(N):\n",
    "    for j in range(N):\n",
    "        r, g, b = 1., 1., 1.\n",
    "        x = xmin + j * (xmax - xmin) / N\n",
    "        y = ymax - i * (ymax - ymin) / N\n",
    "        d = distance(x, y, math.sqrt(3)/2, -1/2) \n",
    "        if d <= R: b = 0.\n",
    "        d = distance(x, y, -math.sqrt(3)/2, -1/2) \n",
    "        if d <= R: g = 0.\n",
    "        d = distance(x, y, 0, 1) \n",
    "        if d <= R: r = 0.\n",
    "        m[i][j] = (r, g, b)\n",
    "plt.imshow(m, extent=extent, interpolation='bicubic')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remarquez que le noir est obtenu en mélangeant du Cyan, du Jaune et du Magenta à parts égales."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2 Le modèle CMYK"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On peut théoriquement obtenir toutes les couleurs possibles, Noir inclus, à partir d'encres Cyan, Jaune et Magenta. Cela pose toutefois des problèmes pratiques. En effet, pour obtenir du noir il faut mélanger trois encres sur une zone du papier d'impression. Pour ne citer que quelques problèmes :\n",
    "\n",
    "- Il est difficile d'obtenir de cette façon un noir vraiment noir. Il faudrait pour cela effectuer un mélange dans des proportions absolument parfaites.\n",
    "- Le fait de déposer 3 encres sur la même zone rend le séchage plus long et fragilise le papier. Sur du papier de qualité médiocre cela peut même provoquer des déchirements.\n",
    "- On frôle le non-sens lorsqu'on imprime des centaines de pages de texte en noir et blanc.\n",
    "- Les encres de couleur coûtent plus cher que l'encre noire.\n",
    "\n",
    "La solution retenue par la quasi-totalité des systèmes d'impression est de travailler avec 4 couleurs : Cyan, Magenta, Jaune, ET Noir. C'est le modèle CMYK. Pour créer une couleur donnée, on met autant de noir que possible, puis on ajuste les quantités de Cyan, Magenta et Jaune pour obtenir la couleur désirée.\n",
    "\n",
    "__Remarque__ : En langue française le modèle CMYK est appelé CMJN. Il est à la base du procédé d'impression par __quadrichromie__."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3 Conversions entre CMYK et CMY"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alors, combien met-on de noir ? Soit $(c, m, y)$ le triplet représentant une couleur dans le système CMY. Soit $(c',m',y',k)$ le quadruplet correspondant dans le système CMYK. Supposons par exemple $c\\le m\\le y$. L'idée est de prendre $k=c$, $c'=0$, $m'=\\lambda(m-k)$ et $y'=\\lambda(y-k)$. On choisit $\\lambda$ pour que $c',m'$ et $y'$ soient entre 0 et 1. La valeur $\\lambda=\\frac 1{1-k}$ fera l'affaire.\n",
    "\n",
    "Notez le cas particulier $k=1$, c'est à dire $c=m=y=1$. Il correspond à la couleur noire.\n",
    "\n",
    "Dans le cas général, on pose $k=\\min(c,m,y)$. La fonction `cmy_to_cmyk` ci-dessous ne présente aucune difficulté."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cmy_to_cmyk(c, m, y):\n",
    "    k = min(c, m, y)\n",
    "    if k == 1: return (0, 0, 0, 1)\n",
    "    else:\n",
    "        c1 = (c - k) / (1 - k)\n",
    "        m1 = (m - k) / (1 - k)\n",
    "        y1 = (y - k) / (1 - k)\n",
    "        return (c1, m1, y1, k)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction réciproque est facile à obtenir. Je laisse mes vaillants lescteurs résoudrent des équations de degré 1 pour obtenir $c,m,y$ en fonction de $c',m',y',k$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cmyk_to_cmy(c, m, y, k):\n",
    "    c1 = min(1, c * (1 - k) + k)\n",
    "    m1 = min(1, m * (1 - k) + k)\n",
    "    y1 = min(1, y * (1 - k) + k)\n",
    "    return (c1, m1, y1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Testons que nos fonctions sont bien réciproques l'une de l'autre ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "c, m, y = random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1)\n",
    "c1, m1, y1, k = cmy_to_cmyk(c, m, y)\n",
    "c2, m2, y2 = cmyk_to_cmy(c1, m1, y1, k)\n",
    "print(c, m, y)\n",
    "print(c2, m2, y2)\n",
    "print(c1, m1, y1, k)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Reamrque__ : Remarquez que l'une des composantes $c,m,y$ dans le modèle CMYK est nulle. Pour imprimer une couleur quelconque, on utilise du noir et DEUC autres couleurs. Dans la vraie vie la situation est un peu plus compliquée, mais je n'entrerai pas dans les détails."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.4 Conversions entre CMY et RGB"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Passer de CMY à RGB et vice-versa est immédiat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def rgb_to_cmy(r, g, b):\n",
    "    c = 1 - r\n",
    "    m = 1 - g\n",
    "    y = 1 - b\n",
    "    return (c, m, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cmy_to_rgb(c, m, y):\n",
    "    r = 1 - c\n",
    "    g = 1 - m\n",
    "    b = 1 - y\n",
    "    return (r, g, b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pour une fois je ne testerai pas mes fonctions :-)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.5 Conversions entre CMYK et RGB"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Il n'y a qu'à composer les fonctions déjà écrites : \n",
    "\n",
    "- RGB $\\to$ CMYK = RGB $\\to$ CMY $\\to$ CMYK\n",
    "- CMYK $\\to$ RGB = CMYK $\\to$ CMY $\\to$ RGB."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def rgb_to_cmyk(r, g, b):\n",
    "    c, m, y = rgb_to_cmy(r, g, b)\n",
    "    return cmy_to_cmyk(c, m, y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cmyk_to_rgb(c, m, y, k):\n",
    "    c1, m1, y1 = cmyk_to_cmy(c, m, y, k)\n",
    "    return cmy_to_rgb(c1, m1, y1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Petit test ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r, g, b = random.uniform(0, 1), random.uniform(0, 1), random.uniform(0, 1)\n",
    "c, m, y, k = rgb_to_cmyk(r, g, b)\n",
    "r1, g1, b1 = cmyk_to_rgb(c, m, y, k)\n",
    "print(r, g, b)\n",
    "print(r1, g1, b1)\n",
    "print(c, m, y, k)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.6 Remarque finale : la réalité est plus compliquée"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dans la réalité les choses sont plus compliquées que cela. Chaque imprimante possède ses propres caractéristiques et ses propres encres. Il en résulte que la quantité d'encre de chaque couleur à choisir pour obtenir une couleur donnée n'est pas la même pour chaque imprimante. Certaines couleurs de l'espace total des couleurs sont même impossibles à obtenir par impression. \n",
    "\n",
    "Aussi, il convient de calibrer l'imprimante. Très schématiquement, on imprime une feuille contenant des couleurs connues à l'avance puis on mesure  à l'aide d'un photomètre quelles couleurs ont été réellement imprimées. À partir de ces mesures on crée un __profil__ de l'imprimante.\n",
    "\n",
    "Chaque imprimante est livrée avec un tel profil, un fichier dont l'extension est en général `.ICC` (International Color Consortium) ou `.ICN`. Le calcul réel du quadruplet $(c,m,y,k)$ utilise les données contenues dans ce fichier afin que les couleurs imprimées soient aussi proches que possible des couleurs désirées.\n",
    "\n",
    "Nos très jolies fonctions de conversion entre RGB et CMYK ne serviront donc à rien dans la pratique. Elles ne donnent que des estimations assez grossières."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ces remarques sont également valables pour les moniteurs. Eh oui, même RGB est trompeur ! Chaque moniteur affiche les couleurs différemment. Et, donc, chaque moniteur est livré avec un profil `ICC` qui permet, étant donné un triplet $(r,g,b)$  d'afficher sur l'écran la couleur qui approche le plus possible la \"vraie\" couleur $(r,g,b)$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
