Resumé

Rotazione del Colore con F# e GDI+

Cristian Merighi () 5,00

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).

RGB Space

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:

  1. Motivo generale: ovviano ai problemi di gimbal lock.
  2. 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!

Visual Studio support

...I still love duck-typing (anche ruotato di 123°)!

Visual Studio support (hue-rotated ;))

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.

Feedbacks

  • Re: Hue Rotation with F# and GDI+

    Carlos Alloatti domenica 13 giugno 2010 5,00

    Excellent article thanks! What if we did not want to rotate, but apply an specific hue value?

feedback
 

Syndicate

Autore

Cristian Merighi facebook twitter google+ youtube

Ultimi articoli

Top rated

Archivio

Dove sono?

Autore

Cristian Merighi facebook twitter google+ youtube

Le mie letture

Feeds