Le diagramme de chromaticité a été défini dans : repérage des couleurs CIE XYZ. Il permet de repérer toutes les couleurs visibles, indépendament de leur niveau de luminance.
On présente ici un module python permettant de générer un diagramme de chromaticité avec représentation colorée des espaces de couleur RGB.
Le module DiaChrom.py est constitué d'une classe.
Le constructeur prend en argument une instance de la classe CieXYZ définie dans repérage des couleurs CIE XYZ.
from numpy import * from pylab import * from math import * from XYZ2RGB import * from CieXYZ import * class DiaChrom: def __init__(self,ciexyz): self.ciexyz = ciexyz self.kmin = 400-360 self.kmax = 701-360 self.ns = self.kmax-self.kmin spectre = ciexyz.xyColor() self.ymin = spectre[1][self.kmin] self.ymax = max(spectre[1]) self.xSpectre = [] self.ySpectre = [] for k in range(self.ns): self.xSpectre.append(spectre[0][k+self.kmin]) self.ySpectre.append(spectre[1][k+self.kmin]) self.x1 = self.xSpectre[0] self.y1 = self.ySpectre[0] self.x2 = self.xSpectre[self.ns-1] self.y2 = self.ySpectre[self.ns-1]
La fonction suivante trace le lieu des couleurs spectrales et la ligne des pourpres :
def plotSpectre(self): plot(self.xSpectre,self.ySpectre,c='k',label='Spectrum locus') plot([self.x1,self.x2],[self.y1,self.y2],c=(1,0.1,0.75),label='Pourpres') for L in [400,480,500,520,550,600,700]: k=L-400 if L<520: text(self.xSpectre[k],self.ySpectre[k],str(L),horizontalalignment='right') else: text(self.xSpectre[k],self.ySpectre[k],str(L),horizontalalignment='left')
La fonction suivante colorie le triangle correspondant à un espace RGB. Les arguments sont une instance de la classe XYZ2RGB représentant cet espace, le nom de cet espace, et le pas de calcul des couleurs.
def paintRGB(self,xyz2rgb,name,delta): rgb = xyz2rgb plot([rgb.red[0],rgb.green[0],rgb.blue[0],rgb.red[0]],[rgb.red[1],rgb.green[1],rgb.blue[1],rgb.red[1]],color='k',marker='o',label=name) img=[] largeur=rgb.red[0]-rgb.blue[0] hauteur=rgb.green[1]-rgb.blue[1] imax=int(floor(largeur/delta)) jmax=int(floor(hauteur/delta)) penteBG = (rgb.green[0]-rgb.blue[0])/(rgb.green[1]-rgb.blue[1]) penteBR = (rgb.red[0]-rgb.blue[0])/(rgb.red[1]-rgb.blue[1]) penteGR = (rgb.red[0]-rgb.green[0])/(rgb.red[1]-rgb.green[1]) for j in range(jmax): ligne=[] y=rgb.blue[1]+float(j)*delta xmin=rgb.blue[0]+penteBG*(y-rgb.blue[1]) if y<rgb.red[1]: xmax=rgb.blue[0]+penteBR*(y-rgb.blue[1]) else: xmax=rgb.green[0]+penteGR*(y-rgb.green[1]) for i in range(imax): x=rgb.blue[0]+float(i)*delta if (x>=xmin)&(x<xmax): RGB=rgb.xyL2rgb(x,y,1.0) ligne.append([RGB[0],RGB[1],RGB[2],1]) else: ligne.append([0,0,0,0]) img.append(ligne) imshow(img,origin='lower',extent=[rgb.blue[0],rgb.red[0],rgb.blue[1],rgb.green[1]])
La fonction suivante colorie le domaine compris entre le spectrum locus et la ligne des pourpres, en utilisant l'espace de couleur précisé. Remarquer que certaines régions sont hors gamut par rapport à cet espace de couleur : les couleurs correspondantes sont donc approximatives (voir la page sur les espaces RGB pour la méthode d'approximation).
def paintAll(self,xyz2rgb,name,delta): rgb=xyz2rgb img=[] largeur=self.x2 hauteur=self.ymax imax=int(floor(largeur/delta)) jmax=int(floor(hauteur/delta)) for j in range(jmax): ligne=[] y=float(j)*delta k=0 while (y>self.ySpectre[k]): k+=1 xmin=self.xSpectre[k] k+=1 if y>self.y2: while (y<self.ySpectre[k]): k+=1 xmax=self.xSpectre[k] else: xmax=self.x1+(y-self.y1)*(self.x2-self.x1)/(self.y2-self.y1) for i in range(imax): x=float(i)*delta if (x>=xmin)&(x<xmax): RGB=rgb.xyL2rgb(x,y,1.0) ligne.append([RGB[0],RGB[1],RGB[2],1]) else: ligne.append([0,0,0,0]) img.append(ligne) imshow(img,origin='lower',extent=[0,largeur,0,hauteur]) plot([rgb.red[0],rgb.green[0],rgb.blue[0],rgb.red[0]],[rgb.red[1],rgb.green[1],rgb.blue[1],rgb.red[1]],color='k',marker='o',label=name)
La fonction suivante trace les points représentatifs pour un corps noir de différentes températures.
def corpsNoir(self,Tarray): x = [] y = [] s = "" for T in Tarray: self.ciexyz.setIlluminant("CN",T) def Rf(L): return 1 XYZ = self.ciexyz.spectralF2XYZ(Rf) xy=self.ciexyz.XYZ2xy(XYZ[0],XYZ[1],XYZ[2]) x.append(xy[0]) y.append(xy[1]) s += "%.0f "%T plot(x,y,marker="+",color="k",linestyle="-",label="CN : %s K"%s)
from DiaChrom import * ciexyz=CIEXYZ("../ciexyz/ciexyz31.txt") sRGB = XYZ2RGB([0.64,0.33],[0.3,0.6],[0.15,0.06],[0.3127,0.329]) diagram=DiaChrom(ciexyz) figure(1,figsize=(9,8)) xlabel('x') ylabel('y') diagram.plotSpectre() diagram.paintRGB(sRGB,"sRGB",0.002) diagram.corpsNoir([6500]) legend() axis([0,1,0,1]) grid(True)plotA.pdf
adobeRGB = XYZ2RGB([0.64,0.33],[0.21,0.71],[0.15,0.06],[0.3127,0.329]) diagram=DiaChrom(ciexyz) figure(2,figsize=(9,8)) xlabel('x') ylabel('y') diagram.plotSpectre() diagram.paintRGB(adobeRGB,"Adobe RGB(98)",0.002) legend() axis([0,1,0,1]) grid(True)plotB.pdf
Les images RGB des deux figures précédentes sont des images RGB sans profil ICC associé. En conséquence, les couleurs les plus saturées (proches de la primaire verte par exemple) sont les mêmes dans les deux cas, quelque soit le moniteur utilisé.
Pour percevoir une éventuelle différence entre les deux espaces de couleur, il faut attribuer un profil ICC aux images. Les deux images suivantes sont obtenues par attribution du profil adéquat (avec le logiciel Photoshop) :
Pour comparer ces deux images, il faut utiliser un système d'exploitation avec gestion des couleurs (MS Windows ou Mac OS), et les ouvrir dans un logiciel reconnaissant les profils ICC (par ex. Photoshop). Le navigateur Firefox 3 (mais pas la version 2) lit ces profils; pour cela la variable de configuration gfx.color_management.enabled doit avoir la valeur true.
Sur un moniteur à large gamut calibré AdobeRGB, une observation attentive des figures ci-dessus montre que l'espace AdobeRGB présente des niveaux de saturation supérieurs (dans les bleu-verts). Par exemple, sur le moniteur Eizo CG241W, l'apport de l'espace AdobeRGB par rapport à sRGB est perceptible.
L'avantage de l'espace AdobeRGB pour les images imprimées est un autre sujet, qui fait intervenir l'espace de couleur de l'imprimante utilisée. Toute comparaison d'images imprimées ne peut se faire que par observation directe ou par mesures avec un spectrophotomètre, avec un illuminant calibré. Scanner ces images pour les comparer à l'écran n'a aucun sens, puisque le gamut des images imprimées est supérieur à ceux des meilleurs écrans LCD.
En utilisant par exemple l'espace AdobeRGB(98), on peut représenter toutes les couleurs visibles. Il faut toutefois garder à l'esprit que les couleurs situées hors du gamut AdobeRGB sont des approximations de couleurs qui en réalité sont plus saturées.
figure(3,figsize=(9,8)) xlabel('x') ylabel('y') diagram.plotSpectre() diagram.paintAll(adobeRGB,"Adobe RGB(98)",0.001) legend() axis([0,1,0,1]) grid(True)plotC.pdf