Rotazione del Colore con F# e GDI+
Cristian Merighi ()

Primo articolo scritto in F#. L'argomento, utile e pretestuoso al contempo, è la rotazione della tonalità e gli oggetti GDI+ del .Net framework.
Questo articolo è da considerarsi obsoleto. Alcune funzionalità potrebbero non essere più disponibili e non è possibile aggiungere commenti.
Non sono molto ferrato sulle questioni informatico-filosofiche che stanno dietro
agli schieramenti a favore o contro le varie tipologie di linguaggi
di programmazione. Diciamo però che mai, prima dell'avvento di F#, mi era capitato d'imbattermi in un linguaggio
di programmazione cosiddetto funzionale.
Mi sono perso qualcosa.
Ho approcciato questa nuova tecnologia per sperimentarne le potenzialità in
termini di gestione del codice asincrono e parallellizzato (in anticipo su PLinq
e sul CLR 4), ma mi sono innamorato
della sua sintassi e dei suoi pattern estremamente naturali e fluenti.
Provo a fissare questa mia prima esperienza in qualche riga di codice relativo alla gestione delle librerie GDI+ del .Net Framework.
Parliamo del "semplice" problema della rotazione della tonalità dei colori, per ognuno dei pixels che compongono il fitto mosaico di un'immagine.
Brevissimo cenno alla teoria: Lo spazio di colori più utilizzato - anche in ambito web - è il cosiddetto RGB (da Red, Greeen, Blue).
Esso è schematizzabile in uno spazio tridimensionale generato dai 3 assi che portano il nome dei 3 colori base.
Il valore di ogni canale dispone di un byte (8 bits) di informazioni (in base 10 il range varia tra 0 e 255, 28 valori).
La linea che congiunge il punto nero (0,0,0) al punto bianco (255, 255, 255)
di tale spazio RGB è il cosiddetto asse neutro (scala dei grigi).
Il problema che andiamo ad affrontare - e risolvere - con F# consiste nel "ruotare" ogni singolo pixel (identificato da tre coordinate nello spazio RGB) di un fissato
numero di gradi attorno all'asse neutro (vedi figura).
Andiamo a gestire tale rotazione scomodando alcuni oggetti tipici dei motori di geometria tridimensionale, come vettori 3D e
quaternioni.
I quaternioni risultano molto comodi in questo contesto per due motivi:
- Motivo generale: ovviano ai problemi di gimbal lock.
- Motivo particolare: pemettono di ottenere agevolmente una matrice rotazionale, ideale nel nostro caso per generare una
ColorMatrix
da applicare al'immagine originale.
Riguardo alla teoria dei quaternioni, beh, rimando agli approfondimenti reperibili ovunque su internet e nelle biblioteche.
Nel codice che segue i commenti saranno destinati a descrivere lo scopo - più che il significato - dell'algoritmo relativo.
#light
namespace Pacem.Drawing
open System.Drawing
module internal ColorMatrix =
let internal FromAngleAxisRotation(i, j, k, theta) =
// quaternion-like
let angleInRadians = (theta % 360.0) * 0.017453292519943295
let length = sqrt (i*i + j*j + k*k)
// love the pipeline pattern ;)
if (length = 0.0) then System.InvalidOperationException() |> raise
let sinTheta = 0.5 * angleInRadians |> sin
// preparing the 4 parameters of the quaternion (x, y, x, w)
let x = i * sinTheta / length
let y = j * sinTheta / length
let z = k * sinTheta / length
let w = 0.5 * angleInRadians |> cos
// quaternion to rotation matrix
let m11 = 1.0 - 2.0 * y * y - 2.0 * z * z |> float32
let m12 = 2.0 * x * y + 2.0 * w * z |> float32
let m13 = 2.0 * x * z - 2.0 * w * y |> float32
let m21 = 2.0 * x * y - 2.0 * w * z |> float32
let m22 = 1.0 - 2.0 * x * x - 2.0 * z * z |> float32
let m23 = 2.0 * y * z + 2.0 * w * x |> float32
let m31 = 2.0 * w * y + 2.0 * x * z |> float32
let m32 = 2.0 * y * z - 2.0 * w * x |> float32
let m33 = 1.0 - 2.0 * x * x - 2.0 * y * y |> float32
// notice array ctor: [| item1; item2; ... itemN |]
let source = [|
[| m11; m12; m13; 0.0f; 0.0f |];
[| m21; m22; m23; 0.0f; 0.0f |];
[| m31; m32; m33; 0.0f; 0.0f |];
[| 0.0f; 0.0f; 0.0f; 1.0f; 0.0f |];
[| 0.0f; 0.0f; 0.0f; 0.0f; 1.0f |]
|]
// result to return
System.Drawing.Imaging.ColorMatrix(source)
I love duck-typing!
...I still love duck-typing (anche ruotato di 123°)!
Ed ora il codice esemplificativo per la renderizzazione/salvataggio del file.
Attenzione: si tratta di uno stralcio di codice decontestualizzato da moduli o tipi (= non compila).
use attr = new ImageAttributes()
// degrees is the variable that carries the angle to rotate by.
attr.SetColorMatrix(Pacem.Drawing.ColorMatrix.FromAngleAxisRotation(1.0, 1.0, 1.0, degrees))
use bmp = new Bitmap("c:\\myOriginalImage.jpg")
let w = bmp.Width
let h = bmp.Height
let rc = Rectangle(0, 0, w, h)
use graphics = bmp |> Graphics.FromImage
graphics.DrawImage(bmp, rc, 0, 0, w, h, GraphicsUnit.Pixel, attr)
bmp.Save("c:\\myHueRotatedImage.jpg")
Forse non si tratta dell'esempio più illuminante in relazione alle potenzialità di F#, ma è almeno un inizio.
Da parte mia ho già totalmente riscritto le mie librerie GDI+ asincronizzando e parallelizzando il codice. Ne sono rimasto meravigliato: sul
mio Quad core
anche l'esecuzione dei cicli algoritmicamente più pesanti si risolve entro gli uno/due decimi di secondo per immagini di dimensioni grandi (1024x768).
Take care. Bye.