I'm not that into those techno-philosophical disputes pro or contra a particular type of programming language.
The thing I know is that I've never tried a functional language before F#.
I've been missing something.
I approached this new technology to experiment asynchronous and parallelized functionalities (waiting for PLinq and the CLR 4),
but I suddenly fell in love with its fluent and natural syntax and patterns.
I just want to subscribe this first experience of mine signing a bunch of code lines relevant to GDI+ image management, specifically
Hue rotation.
Brief theory excursus: most known and used among the color spaces
is without doubt the so named RGB (from Red, Greeen, Blue).
This space can be sketched as a 3D environment generated by the three axes identified by the base colors.
Each color channel has available 1 byte (8 bits) of informations (in base-10, 28 values: from 0 to 255).
The imaginary line which connects the black point (R = 0, G = 0, B = 0)
and the white one (255, 255, 255) is the gray axis.
Hue rotation is all about moving (rotating) colors around the gray axis (see figure).
To handle this kind of rotation let us disturb our fellows 3D vectors and
quaternions.
Quaternions are very useful in this scenario:
- General reason: they obviate the gimbal lock problem.
- Specific reason: they easily permits to get a correspondent 3D rotation matrix, ideal for us: we in fact need to generate a
ColorMatrix
and apply it to the original image.
About quaternions' theory, I'd address you to resources and literature aailable on the web or in the libraries,
the following code will be commented specifying the goal of the line more than its algebraic meaning.
#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 (now rotated by 123°)!
Now how to use it to load, manipulate and save images.
Warning: this is decontextualized code, outside any module or type (= won't compile).
use attr = new ImageAttributes()
// degrees is the variable that carries the angle to rotate by.
// (1.0, 1.0, 1.0) has obviously the same direction of (255.0, 255.0, 255.0).
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")
Maybe this is not the more enlightning example about F# potentials, but it's a start.
About me, I've already rewritten my GDI+ libraries asynchronizing and parallelizing wherever possible, and I was stunned:
on my Quad core, even the heavier cycles solve in one or two tenths of second for pretty huge images (1024x768).
Take care. Bye.
Feedbacks