2015年賀状をHaskellで上の画像が今年の僕達の年賀状です。画像のダウンロードができず、Pixelmatorで作りなおそうという話がでました。試しにやってみることにしました。面白かったので、やり方を公開します。概要まず、上の画像をみて、デザインを考えます。最初は、下記のやりかたでやろうと思っていました。写真の上に、真っ白のレイヤーを載せる。その白いレイヤーから3つの三角形を切って、下の写真が見えるようになる。最後にメッセージを追加する。(というか、あるかもしれないけど、僕が分からなかったので)。「そのまま5つの三角形を描く」という方法でやりました。文章ではちょっと分かりづらいと思うので、絵で説明します。この下にメッセージをつけます。実際にどう作ったか振り返ります。依存関係まず、Haskellのライブラリをインストールします。下記のcabal設定でインストールしました。default-language:Haskell2010arithmoiは最近llvmのバージョンによってエラーになるときがあります。installしてください。-llvm"フォントをsvgに変換するため、fontforgeが必要です。Macを使ってたので、まずXQuartzをインストールしました。fontforgeを入力して、インストールします。予備LANGUAGEプラグマは、意外と多くなりました。diagramsのドキュメンテーションに推奨されるNoMonomorphismRestriction。入れましたが必要なかったので、結果的に使いませんでした。FunctorとTraversableの関数を使いたいと思いました。Data.Char終わり。今回のプロジェクトは殆どの型はdiagramsで定義されています。自分で定義をしたのは一つだけです。それは、フォントを設定するための型です。準備ができた使える状態のフォントを入れたいと思います。}このnewtypeを使って、「フォントが変換された」と、型安全的に証明します。最新版がHackageにアップされたらこれは必要なくなります。)それでは、コード自体を始めましょう。二等辺三角形もう一度下の画像を見てみましょう。二等辺三角形はないため、自分で定義する必要があります。(θ^/2)θは頂角、aは高度(altitude)です。角度やベクトル等を引算、割算ができるような関数です。(&)、(.~)はlensのオペレーターです。今からlensの説明しようとすると話が終わらないので、今日は省きます。画像のレイアウトをするため、底辺の長さが必要なときがあります。それを計算するために下記のユーティリティ関数を使います。aレイアウト簡単に言うと、このデザインは「上に画像があって、その下にメッセージ」と説明できます。フォントや写真はもう既にロードされていると見なしましょう。すると、下記のようになります。3)(===)は、2つのDiagramを上から下に並べる関数です。topImageは上の画像、messageは下のメッセージ。並べると、年賀状になると。あとは、ここで使った値を定義するだけです。photoHeightまずは、写真や画像についての変数です。θの値を変更したら、三角形の形を変えられます。imageWidthはできた画像(緑のメイン三角形)の幅となります。細かいことになりますが、これで調整しています。inViewportで、外の部分を消す関数を用意しました。画像自体も簡単に説明できます。写真を切る必要があります。その切った写真の上に、三角形を載せていきます。where切るサイズは、写真の元の高さ✕緑の三角形の底辺の長さです。それではちょっと幅が見えてしまうので、あと2ピクセルずつ、念の為に切ります。edgeTriangleRight>]になります。場合によっては必要になりますね。centralTriangleを描く...という形)。この画像の三角形を見ると2種類があります。真ん中の3つの三角形。輪郭のみを描いて、下にある写真が見える三角形ですね。原点は高度から見る真ん中ではなく、重心に設定してあります。二等辺三角形を鏡映すると、ずれてしまう。centerXYをしたら、簡単に鏡映できるようになります。次は左と右の、真っ白の三角形です。この三角形の頂角は真ん中の三角形の反対角度になっています。高度は下の三角形の底辺の半分です。この値はまた使うので変数に保存しておきましょう。4あとは色と90°の回転です。deg)outlineTriangleとedgeTriangleの2種類を定義できました。これで5つの三角形が描けます。高度は写真と一緒です。reflectYあとは右側の下の三角形と真っ白の三角形。軸に鏡映するだけです。edgeTriangleRight画像の分はここまでです。実は、メッセージを定義するため、今回下記のユーティリティー型を定義しました。絶対必要とは言えません。このプログラムを書いたときはこの型は使わず作りました。ブログのためにちょっとリファクタリングしてみました。MessagePartはメッセージの一行です。何も表記しない、ただスペース開けるため(MsgSpace)です。この型について一つポイントがあります。proportionalHeightとproportionalWidthは、「高さ」と「幅」の割合を意味します。表記の仕方はそれぞれ違います。幅の半分としたいなら1/2と表記します。proportionalHeightは、表記スペースに対して一行の大きさを決るため、表記スペースに対するの「割合の分子」のみ表記します。分母は、全部のメッセージのproportionalHeightの合計になるはずです。結果、MessagePartを並べれば、スペースを100%と使えていることになります。この型があったらメッセージ自体は、ただMessagePartのリストになります。(===)で上から下まで並べます。軸にスペースを開けます。MsgTextだったらtext'としてレンダーします。getRealHeightは、割合の分子から、実際の高さに変換する関数です。messagePartstext'は、SVGFontsを使ってテキストをレンダーします。d不純なところ今までの関数は純粋的に定義しました。写真やフォントを準備するための関数を定義します。エラーが返されたらそのまま出力して停止します。1)フォントはもうちょっと複雑なんです。whereフォントは.ttfから.svgに変換しないと使えないんです。変換されたフォントはsvg-fontsというディレクトリーに出力します。そのディレクトリーがない場合は作成しないとダメです。fontDir変換自体はfontforgeを使って行います。変換されたフォントがあるならまた変換する必要はありません。SVGFontsがネームスペース無しのXMLしかサポートされていません。下記の関数はネームスペース宣言を外します。最後に、上記の関数を結んでいくmain関数です。写真とフォントの準備をし、nengajouの関数に渡してDiagramを作成します。padを使って枠を作ります。それから背景を白にしましょう。最終、DiagramsのmainWith関数を使ってコマンドラインインターフェースが出来上がります。