Advent Art Generating Hokusai paintings
For the last few years, the Japanese F# community has been running the F# Advent Calendar (2010, 2011, 2012). Each advent day, one person writes an article about something interesting in F#. I have been following the advent calendar last year on Twitter and when the planning started for this year, I offered to write an article too. You might have noticed that I posted a Japanese version of the article in December as part of the advent calendar 2013.
A number of people helped to make this happen - @igeta arranged everything and helped with reviewing, @yukitos translated the article and @gab_km reviewed the translation. Thanks!
But what should I write about? It would be nice to look at some of the F# open-source libraries and projects that have been developing over the last year in the F# community. At the same time, can I relate the topic of the article to Japan? After some thinking, I came up with the following plan:
-
First, we'll use the F# Data library and the Freebase to learn something about Japanese art. I should add that thanks to @yukitos the library now also has a documentation in Japanese.
-
Then we'll pick one art work and try to recreate it in F#. Given my artistic skills, this part will definitely fail, but we can try :-).
-
Finally, we'll use the FunScript project to turn our F# code into JavaScript, so that we can run it as a pure HTML web application that also works on mobile phones and other devices.
Exploring Japanese art
Freebase is an online graph database with schematized information, mostly based on data from Wikipedia. It contains information about a wide range of topics including society (governments, celebrities, etc.), sports (different kinds), science (computers, chemistry, etc.) and also art.
The F# type provider for Freebase in F# Data gives us a way to access all this knowledge directly
from a code editor. To use it, you can install NuGet package FSharp.Data
and reference the
assembly as follows:
1: 2: 3: 4: 5: |
|
Aside from FSharp.Data
, we'll also use some of the LINQ methods to explore the data.
The Freebase can be accessed using FreebaseData
or using FreebaseDataProvider
- the
latter one takes an API key which is needed for code that makes larger number of
requests. To run the complete sample, you'll probably need to register:
1: 2: |
|
Now you can type fb.
and see a list of areas that are available in Freebase.
For example, we can look at the "Visual Art" category that contains paintings and
painters, get the first painter in the list (called "Visual Artists") and explore
some information about the painter:
1: 2: 3: 4: 5: |
|
If you try running the code, you'll see that the first painter returned from the list
is Leonardo da Vinci. The Country of nationality
property actually returns a list,
but here we get just a single country, which is Italy. We can also get a list of paintings
using the Artworks
property.
Next, we can write a query that will find all artists with Japanese nationality (to make things simpler, we only check the first nationality in the list):
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
When you run the snippet, the first artists that are returned are Yoshitaka Amano, Isamu
Noguchi and Takashi Murakami, all born in the 20th century. We could add sortBy
or
sortByDescending
to specify the order. We can also search for a specified artist
and use head
to get detailed information about a single artist:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
The second part of the snippet prints different information about Hokusai in HTML format. The results look as follows:
Katsushika Hokusai (葛飾 北斎, September 23, 1760 – May 10, 1849) was a Japanese artist, ukiyo-e painter and printmaker of the Edo period. He was influenced by such painters as Sesshu, and other styles of Chinese painting. Born in Edo (now Tokyo), Hokusai is best known as author of the woodblock print series Thirty-six Views of Mount Fuji (富嶽三十六景, Fugaku Sanjūroku-kei, c. 1831) which includes the internationally recognized print, The Great Wave off Kanagawa, created during the 1820s.
The Dream of the Fisherman's Wife
The Great Wave off Kanagawa
Travellers Crossing the Oi River
Red Fuji
Feminine Wave
Masculine Wave
Black Fuji
Oceans of Wisdom
Hokusai and fractals
If you look at the paintings, you can see that some of them look like fractals. Searching for Hokusai and fractal on Google images gives us a number of fractals that are similar to some of the Hokusai's paintings.
In this article, I'll use the Julia set fractal. The fractal is quite easy to draw (especially using F#). To make it look similar to The Great Wave off Kanagawa, we'll use a carefully chosen color palette to colorize the fractal.
Calculating Julia set
You can find more information about Julia sets on Wikipedia, so I will not try to explain how exactly it works - the idea is that we generate a sequence of complex numbers - at each step we calculate the next value as cnext = cprev2 + c where c is some constant. The iteration can be nicely written as a recursive sequence expression that generates (potentially) infinite sequence:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
The function takes coordinates (in range from -1 to +1) and generates a sequence for one pixel
on the screen. Next, we need to count the number of iterations until the absolute value of the
number is greater than 2 (or until the maximum number of iterations exceeds a specified limit).
To do that, we can use functions from the Seq
module:
1: 2: 3: 4: 5: |
|
First, we restrict the number of iterations to the specified number, then we keep taking values
with small absolute value and, finally, we get the length of the sequence (when the absolute
value exceeds 2.0, the takeWhile
function will end the sequence and length
will be smaller
than max - 1
). We could write the code in an imperative way - and it would likely be faster - but
this way, it clearly expresses the definition of Julia set.
Generating color palette
To draw the fractal, we'll iterate over all pixels where we want to draw, call
countInterations
with a specified x
and y
arguments and then pick a color from a palette
based on the number of the iterations. To make the fractal that resembles Hokusai's paintings,
we need to carefully generate the palette - the idea is that we'll create a transition between
a number of colors that are used in The Great Wave off Kanagawa.
For this, we'll use a combination of two custom F# operators that let us write clr1 -- count --> clr2
.
The expression generates a color transition between clr1
and clr2
that has count
number
of steps:
1: 2: 3: 4: 5: 6: 7: 8: |
|
The --
operator is a simple syntactic trick that just builds a tuple. Because the expression
is parsed as (clr1 -- count) --> clr2
, the second operator gets both the initial color and
the count as arguments and it can then generate a list with color transitions.
Generating the palette can now be nicely done using array expression with yield!
to compose
individual transitions. We start with the color of the sky, then generate gradient with different
shades of blue (for water) and then add a number of white shades:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
Drawing the fractal
Now we have everything we need to draw the fractal. To keep the first version simple, let's just
use Windows Forms and Bitmap
from System.Drawing
. I'll skip over the code that creates the
form (but you can find the full source code on GitHub).
After we create the form and bitmap, we simply iterate over all pixels of the bitmap, count the number of iterations (using the palette length as the maximal number) and then index into the palette to get the color:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
|
The parameters w
and h
are tuples that specify the part of the fractal that we want to
draw. You can change the values to -2.0, 2.0
and -1.5, 1.5
to see the entire fractal.
Here, I picked a special part of the fractal to get the following nice picture:
Adding some Fun(Script)
So, we now have a code that draws the fractal, but you probably want to see it running live,
without creating a new Windows Forms project. Luckily, we can use FunScript
which is an F# to JavaScript compiler and we can render our fractal using HTML5 <canvas>
element.
FunScript already has an example that draws Mandelbrot fractals using HTML5, so this will be
quite easy - but the key thing is, we can just reuse all the code we wrote so far. As I don't have
space to describe all the details, here are some important links:
- For more information about FunScript, see the project homepage
- The source code for the FunScript version is available in my GitHub.
- You can also have a look at the live running version of the code.
Let's start by looking how the rendering function changes. As you can see in the live JavaScript version, the rendering is a bit slow and so we update the displayed picture after drawing each line (since we just copied the original code to dynamically typed JavaScript, the slow down is expected - we could use imperative style to make the code faster).
However, we can elegantly change the code to update the picture by wrapping the rendering
function in an F# async
workflow and adding Async.Sleep
after the drawing of a single
line is finished:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: |
|
The code is mostly the same as previously. Note that FunScript has typed wrappers for most
of the JavaScript libraries, so all variables in the code are statically typed (e.g. canv
is HTMLCanvasElement
and has methods like getContext_2d
for getting rendering context).
We use a helper function setPixel
to set a color of a single pixel and a dynamic lookup
operator ?
to get HTML element by ID (both shown in the next paragraph). To start the
rendering, we need to setup an event handler and call StartImmediate
:
1: 2: 3: 4: |
|
The two helper functions that we need are fairly simple too - setPixel
calculates the offset
in ImageData
array and sets the R, G and B components (as well as the alpha channel). The
dynamic operator just calls getElementById
and casts the returned element to the expected
type:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
|
Conclusions
First of all, I hope that you had as much fun reading the article as I had writing it and I wish you a very nice Christmas season!
As I mentioned in the introduction, I wanted to show some interesting libraries that have been recently developed by the F# community and I wanted to do this using a theme inspired by Japan. We started by using F# Data and, more specifically, the Freebase type provider, to get a list of Japanese visual artists and their art works.
Then we picked Hokusai and tried to recreate his Great Wave off Kanagawa using Julia set with an appropriately chosen palette. Our first version was rendered using Windows Forms. To make the code runnable directly in a web browser, we used FunScript to translate F# to JavaScript - and we did not have to do almost any changes in the code. We only added asynchronous workflows (as a bonus) so that we can see how the rendering of the fractal progresses.
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp.Data
--------------------
namespace Microsoft.FSharp.Data
Full name: Japan-advent-art-en.FreebaseData
Full name: FSharp.Data.FreebaseDataProvider
<summary>Typed representation of Freebase data with additional configuration parameters</summary>
<param name='Key'>The API key for the MQL metadata service (default: none)</param>
<param name='ServiceUrl'>The service URL for the MQL metadata service (default: https://www.googleapis.com/freebase/v1)</param>
<param name='NumIndividuals'>The maximum number of sample individuals for each Freebase type (default: 1000)</param>
<param name='UseUnitsOfMeasure'>Use the unit-of-measure annotations from the data source metadata (default: true)</param>
<param name='Pluralize'>Use adhoc rules to pluralize the names of types when forming names of collections (default: true)</param>
<param name='SnapshotDate'>Use a snapshot of the web data store at the given date and/or time in ISO8601 format, e.g., 2012-01-18, 2012-09-15T21:11:32. A value of 'now' indicates the compile time of the code. (default: no snapshot)</param>
<param name='LocalCache'>Use a persistent local cache for schema requests. Also provides the default for whether a persistent local cache is used at runtime. A per-session cache is always used for schema data but it will not persist if this is set to 'false'. (default: true)</param>
<param name='AllowLocalQueryEvaluation'>Allow local evalution of some parts of a query. If false, then an exception will be raised if a query can't be evaluated fully on the server. If true, data sets may be implicitly brought to the client for processing. (default: true)</param>
Full name: Japan-advent-art-en.fb
FreebaseDataProvider<...>.GetDataContext() : FreebaseDataProvider<...>.ServiceTypes.FreebaseService
--------------------
FreebaseData.GetDataContext() : FreebaseData.ServiceTypes.FreebaseService
Full name: Japan-advent-art-en.art
Full name: Japan-advent-art-en.ldv
Full name: Japan-advent-art-en.artists
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.query
Calls Linq.QueryBuilder.Where
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: Japan-advent-art-en.hok
Calls Linq.QueryBuilder.Head
Full name: Japan-advent-art-en.c
Constant that generates nice fractal
type Complex =
struct
new : real:float * imaginary:float -> Complex
member Equals : obj:obj -> bool + 1 overload
member GetHashCode : unit -> int
member Imaginary : float
member Magnitude : float
member Phase : float
member Real : float
member ToString : unit -> string + 3 overloads
static val Zero : Complex
static val One : Complex
...
end
Full name: System.Numerics.Complex
--------------------
Complex()
Complex(real: float, imaginary: float) : unit
Full name: Japan-advent-art-en.iterate
Generates sequence for given coordinates
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>
Full name: Japan-advent-art-en.countIterations
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.take
Full name: Microsoft.FSharp.Collections.Seq.takeWhile
Full name: Microsoft.FSharp.Collections.Seq.length
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = System.Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
struct
member A : byte
member B : byte
member Equals : obj:obj -> bool
member G : byte
member GetBrightness : unit -> float32
member GetHashCode : unit -> int
member GetHue : unit -> float32
member GetSaturation : unit -> float32
member IsEmpty : bool
member IsKnownColor : bool
...
end
Full name: System.Drawing.Color
Color.FromArgb(alpha: int, baseColor: Color) : Color
Color.FromArgb(red: int, green: int, blue: int) : Color
Color.FromArgb(alpha: int, red: int, green: int, blue: int) : Color
Full name: Japan-advent-art-en.palette
let f = new Form(Visible=true, ClientSize=Size(400, 300))
let i = new PictureBox()
i.SizeMode <- PictureBoxSizeMode.Zoom
i.Dock <- DockStyle.Fill
f.Controls.Add(i)
Full name: Japan-advent-art-en.w
Full name: Japan-advent-art-en.h
Full name: Japan-advent-art-en.width
Full name: Japan-advent-art-en.f
Full name: Japan-advent-art-en.height
Full name: Japan-advent-art-en.bmp
type Bitmap =
inherit Image
new : filename:string -> Bitmap + 11 overloads
member Clone : rect:Rectangle * format:PixelFormat -> Bitmap + 1 overload
member GetHbitmap : unit -> nativeint + 1 overload
member GetHicon : unit -> nativeint
member GetPixel : x:int * y:int -> Color
member LockBits : rect:Rectangle * flags:ImageLockMode * format:PixelFormat -> BitmapData + 1 overload
member MakeTransparent : unit -> unit + 1 overload
member SetPixel : x:int * y:int * color:Color -> unit
member SetResolution : xDpi:float32 * yDpi:float32 -> unit
member UnlockBits : bitmapdata:BitmapData -> unit
...
Full name: System.Drawing.Bitmap
--------------------
Bitmap(filename: string) : unit
(+0 other overloads)
Bitmap(stream: System.IO.Stream) : unit
(+0 other overloads)
Bitmap(original: Image) : unit
(+0 other overloads)
Bitmap(filename: string, useIcm: bool) : unit
(+0 other overloads)
Bitmap(type: System.Type, resource: string) : unit
(+0 other overloads)
Bitmap(stream: System.IO.Stream, useIcm: bool) : unit
(+0 other overloads)
Bitmap(width: int, height: int) : unit
(+0 other overloads)
Bitmap(original: Image, newSize: Size) : unit
(+0 other overloads)
Bitmap(width: int, height: int, format: Imaging.PixelFormat) : unit
(+0 other overloads)
Bitmap(width: int, height: int, g: Graphics) : unit
(+0 other overloads)
Full name: Microsoft.FSharp.Core.Operators.snd
Full name: Microsoft.FSharp.Core.Operators.fst
Full name: Japan-advent-art-en.i
Full name: Japan-advent-art-en.palette
Full name: Japan-advent-art-en.setPixel
Full name: ImageData
interface
inherit MSNodeExtensions
inherit MSResourceMetadata
inherit DocumentEvent
inherit MSEventAttachmentTarget
inherit NodeSelector
inherit Node
end
Full name: Document
Full name: Microsoft.FSharp.Core.Operators.failwith
Full name: Japan-advent-art-en.render
Render fractal asynchronously with sleep after every line
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
interface
inherit HTMLElement
end
Full name: HTMLCanvasElement
Full name: Globals
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken
Full name: Microsoft.FSharp.Control.Async
--------------------
type Async<'T>
Full name: Microsoft.FSharp.Control.Async<_>
Full name: Japan-advent-art-en.go
interface
inherit MSDataBindingExtensions
inherit HTMLElement
end
Full name: HTMLButtonElement
Full name: Japan-advent-art-en.setPixel
Set pixel value in ImageData to a given color