App per daltonici: individuare il nome di colore dal valore RGB

Tempo fa mi sono cimentato nello sviluppo di una semplicissima app per daltonici il cui unisco scopo era quello di individuare il nome del colore dominante in una foto. In pratica l’utente che avesse scaricato l’applicazione avrebbe dovuto scattare una foto e la mia app gli avrebbe detto di che colore era.

Il senso? Il mio amico daltonico Davide non può comprarsi vestiti da solo: deve sempre chiedere di che colore sia questo o quel pantalone/maglietta o qualsiasi altra cosa. Non può neppure cucinarsi un hamburger da solo perché non riesce a capire se la carne è cotta dal colore (in realtà potrebbe assaggiarla – ma lui dice che non è lo stesso, giustamente).

Classico esempio del problema della luce
Classico esempio del problema della luce

Dopo alcuni test ho realizzato che effettuare una fotografia, per quanto sia la scelta più intuitiva, non è la scelta migliore per raggiungere il mio obiettivo – infatti il colore della luce influenza il colore percepito dall’occhio della camera – cosicché un colore può apparire in un modo ma essere un altro. La scelta giusta sarebbe utilizzare uno spettrometro… giustamente non incluso in nessuno smartphone, così l’idea si è arenata dopo qualche test sul prototipo.

In ogni caso è stato interessante porsi questa domanda: come posso risalire al nome di un colore semplicemente dal suo valore RGB?

Attacco al problema

La cosa chiara fin da subito è che c’è bisogno di una lista di valori noti a cui confrontare il valore RGB ignoto. Una volta ottenuta la lista, ci si può chiedere (ed è la domanda chiave): come effettuo il confronto?

Sarebbe da pazzi e probabilmente inutile confrontare i singoli valori della terna con quelli disponibili, magari all’interno di uno switch lunghissimo, per ottenere qualcosa… Già solo provarci è da pazzi.

La soluzione più rapida e quella di immaginare la nostra tripletta RGB all’interno dello spazio tridimensionale di assi [(R,0,0),(0,G,0),(0,0,B)]. A questo punto ogni colore non è altro che un vettore! E come sapete passare ad un PC un oggetto matematico è davvero la cosa più facile e veloce.

Allora il procedimento sarà proprio questo. Bisognerà creare una lista di vettori del nostro spazio RGB (che poi è uno spazio tridimensionale normato) contenente i colori che ci vengono in mente (inclusi i grigi) in modo che la distribuzione sia “abbastanza uniforme”. A questo punto prendiamo il vettore-colore sconosciuto e calcoliamo la distanza pitagorica del vettore con quelli in lista. Il nome del colore sarà quello del vettore meno distante. Formulato in JavaScript:

DefaultColors = [
  new Color(0,0,0, "nero"),
  new Color(255,255,255, "bianco"),
  new Color(255,0,0, "rosso"),
  new Color(0,255,0, "verde"),
  new Color(0,0,255, "blue"),
  new Color(255,255, 0, "giallo"),
  new Color(255, 128, 0, "arancione"),
  new Color(102,102,102, "ocra"),
  new Color(75, 153, 0, "verde scuro"),
  new Color(0, 204, 102, "verde acqua"),
  new Color(0, 204,204, "celeste"),
  new Color(0, 51, 102, "blu scuro"),
  new Color(153, 153,255, "lilla"),
  new Color(102, 0, 102, "viola"),
  new Color(204, 0, 204, "fucsia"),
  new Color(255, 152, 255, "lilla"),
  new Color(153, 0, 76, "porpora"),
  new Color(96,96,96, "grigio"),
  new Color(192,192,192, "grigio chiaro"),
  new Color(102, 51, 0, "marrone"),
  new Color(153, 102, 51, "marrone chiaro"),
  new Color(204, 204, 0, "giallo verde"),
  new Color(204, 51, 0, "terra rossa"),
  new Color(51, 153, 51, "smeraldo"),
  new Color(102, 153, 153, "azzurro petrolio"),
  new Color(255, 204, 204, "rosa"),
  new Color(0, 51, 204, "blu nitido")]

function Color(red, green, blue, name) {
  this.Red = red
  this.Green = green
  this.Blue = blue
  if(name != null) { this.Name = name }

  this.Distance = function(color) {
      return Math.sqrt(this.Pow(this.Red - color.Red) + this.Pow(this.Green - color.Green) + this.Pow(this.Blue - color.Blue))
  }

  this.GetApproxName = function () {
      var min = null
      if (this.Name != undefined) return this.Name
      for (i in DefaultColors) {
          if (this.Distance(DefaultColors[i]) < min || min == null) {
              min = this.Distance(DefaultColors[i])
              this.Name = DefaultColors[i].Name
          }
      }
      return this.Name
  }

  this.Darkness = function () {
      toBlackDistance = this.Distance(DefaultColors[0]);
      maxDistance = DefaultColors[0].Distance(DefaultColors[1])
      ratio = 1 - toBlackDistance / maxDistance;
      return ratio.toFixed(2);
  } 
  
  this.Lightness = function () {
      toWhiteDistance = this.Distance(DefaultColors[1]);
      maxDistance = DefaultColors[0].Distance(DefaultColors[1])
      ratio = 1 - toWhiteDistance / maxDistance;
      return  ratio.toFixed(2);
  }

  this.Pow = function(what) {
      return what*what
  }
}

L’esecuzione per esempio potrebbe essere:

var cc = new Color(102, 0, 51)
console.log("nome approx:" + cc.GetApproxName())
console.log("chiarezza:" + cc.Lightness())
console.log("scurezza:" + cc.Darkness())

Ho anche inserito un paio di metodi per scoprire quanto un colore sia chiaro o scuro. Potrebbe esservi utile nel caso vogliate adattare il colore del testo allo sfondo (nel qual caso nella grigli dei colori basterà lasciare anche solo bianco e nero.

Considerazioni finali

Ovviamente lo script è tanto più preciso quanto più la distribuzione dei colori noti (ovvero i vettori della matrice) è egualmente distribuita nello spazio.

Non è il caso del mio esempio: per una migliore (e più lunga) implementazione bisognerebbe dividere ogni asse in, per esempio, 10 parti e quindi effettuare il prodotto cartesiano fra i punti trovati. Adesso si deve inserire per ogni terna il nome del colore corrispondente (a mano!) e avere così lo script ottimizzato.

Leave a Reply

Your email address will not be published. Required fields are marked *