私はHaskellで画像処理プログラムに取り組んでいます。Repa-DevIL ライブラリは、画像処理に適したライブラリです。ただし、処理中の画像をリアルタイムで表示できる GUI が必要です。gtkImage を使用して Repa.Array タイプの画像を表示するにはどうすればよいですか?
1 に答える
RepaArray
をPixbuf
gtkの に変換するのImage
はかなり簡単です。Array
この関数は、 がすでに 32 ビット RGBA データを保持していることを前提としています。
-- full source with language extensions and includes is below
pixbufNewFromArray :: (Source r Word32) => Array r DIM2 Word32 -> IO Pixbuf
pixbufNewFromArray array = do
let (Z:. width :. height) = extent array
pixbuf <- pixbufNew ColorspaceRgb True 8 width height
rowStrideBytes <- pixbufGetRowstride pixbuf
let rowStride = rowStrideBytes `quot` 4
pixbufPixels <- pixbufGetPixels pixbuf
let copyPixel (x, y) = do
writeArray pixbufPixels (y * rowStride + x) (index array (Z:. x :. y))
mapM_ copyPixel $ range ((0, 0), (width-1, height-1))
return pixbuf
残念ながら、これは Repa を gtk2hs で整数化するために必要なすべてではありません。メインの gtk スレッドまたはそのイベント ハンドラーのコンテキストで Repa 計算を実行すると、gtk がロックされます。これに対する解決策は、すべての Repa 計算をバックグラウンド スレッドで実行し、UI の更新をメイン スレッドに送信して実行することです。これの本質はforkIO
、メイン スレッドで、UI の更新を で送り返すことpostGUIAsync
です。
完全な例
完全な例は 3 つの部分に分かれています。Repa を gtk2hs に接続する前に、リアルタイムで表示するデータが必要です。次に、Repa の例を gtk2hs に接続します。最後に、データを表示するための最小限の適切な gtk アプリケーションを提供します。以下は、この例に必要なすべてのディレクティブimport
とディレクティブです。例の問題のステンシルにのみ必要です。LANGUAGE
QuasiQuotes
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
-- Repa life example
import Data.Array.Repa.Stencil
import Data.Array.Repa.Stencil.Dim2
import Data.Vector.Unboxed (Unbox)
-- Repa
import Data.Word
import Data.Array.Repa hiding (map)
import qualified Data.Array.Repa as A
-- GTK
import Graphics.UI.Gtk
import Data.Ix (range)
import Data.Array.MArray (writeArray)
import Control.Concurrent
気晴らし - 人生のゲーム
データの例として、面白いメトセラを使ったライフ ゲーム シミュレーションを使用します。ライフ ゲームは、ピクセルの近傍に適用される単純なルールです。各ピクセルは、生命が存在しない場合は 0、生命が存在する場合は 1 です。このルールは、近傍の数とピクセルの値のみに依存します。適切なステンシルを使用した畳み込みによって、近隣から必要なすべての情報を 1 つの数値にまとめることができます。
lifeStep :: (Source r1 a, Num a, Eq a) => Array r1 DIM2 a -> Array (TR PC5) DIM2 a
lifeStep = smap rule . mapStencil2 (BoundConst 0)
[stencil2| 1 1 1
1 16 1
1 1 1 |]
where
{-# INLINE rule #-}
rule 19 = 1
rule 3 = 1
rule 18 = 1
rule _ = 0
まず興味深いサンプル データは、「r」ペントミノの形をした生命です。
rPentomino :: (Num a, Unbox a) => Array U DIM2 a
rPentomino = fromListUnboxed (Z :. 3 :. 3)
[0, 1, 1,
1, 1, 0,
0, 1, 0]
この人生は、その周りの世界に拡大しようとしています。拡大する余地を与えるために、追加の余地を埋めることができればいいと思います。
pad2 :: (Source r1 a) => a -> Int -> Int -> Int -> Int -> Array r1 DIM2 a -> Array D DIM2 a
pad2 a left right bottom top middle =
traverse middle shape fill
where
extents = extent middle
(Z :. width :. height) = extents
shape = const (Z :. left + width + right :. bottom + height + top)
{-# INLINE fill #-}
fill lookup (Z :. x :. y) =
if inShape extents newPoint
then lookup newPoint
else a
where
newPoint = (Z :. x - left :. y - bottom)
pentominoWorld0 :: (Num a, Unbox a) => Array D DIM2 a
pentominoWorld0 = pad2 0 100 100 100 100 rPentomino
私たちは自分の人生を白い背景に黒いピクセルとして描くつもりです。以下は、生細胞または死細胞から RGBA のこれらの色へのマッピングを実装します。
lifeBonW :: (Source r1 a, Num a, Eq a, Shape sh) => Array r1 sh a -> Array D sh Word32
lifeBonW = A.map color
where
{-# INLINE color #-}
color 0 = 0xFFFFFFFF
color _ = 0xFF000000
gtk2hsにリパ
repa を gtk2hs に接続するには、 Repa を gtk2hs で使用される に変換できる必要がありArray
ますPixbuf
。次にPixbuf
、画像に を描画する必要があります。以下は RGBAの をArray
RGBAに変換します。RGBAは、一度に 1 つのピクセルのチャネルだけではなく、一度に 1 つのピクセルを書き込むことができるため、望ましいものです。Word32
Pixbuf
Pixbuf
pixbufNewFromArray :: (Source r Word32) => Array r DIM2 Word32 -> IO Pixbuf
pixbufNewFromArray array = do
let (Z:. width :. height) = extent array
pixbuf <- pixbufNew ColorspaceRgb True 8 width height
rowStrideBytes <- pixbufGetRowstride pixbuf
let rowStride = rowStrideBytes `quot` 4
pixbufPixels <- pixbufGetPixels pixbuf
let copyPixel (x, y) = do
writeArray pixbufPixels (y * rowStride + x) (index array (Z:. x :. y))
mapM_ copyPixel $ range ((0, 0), (width-1, height-1))
return pixbuf
sをレンダリングする方法が与えられるとPixbuf
、次のコード片は人生ゲームのシミュレーションを実行し、Array
s をに変換しPixbuf
、フレーム間で待機します。
renderThread :: (Pixbuf -> IO ()) -> IO ()
renderThread draw =
do
world0 <- computeP . ofWord8s $ pentominoWorld0
go world0
where
go world = do
pixbuf <- pixbufNewFromArray . lifeBonW . unboxed $ world
draw pixbuf
nextWorld <- computeP . lifeStep $ world
threadDelay 50000 -- microseconds
go nextWorld
unboxed
およびofWord8s
は、そうでなければ非常に多様な RepaArray
の型を指定する便利な方法です。
unboxed :: Array U sh a -> Array U sh a
unboxed = id
ofWord8s :: Array r sh Word8 -> Array r sh Word8
ofWord8s = id
GTK
GTK コードは最小限で、単一のWindow
とImage
. 重要なことはすべてラインで行われforkIO . renderThread $ postGUIAsync . imageSetFromPixbuf image
ます。前述の を開始し、正しい gtk スレッドで表示内容を確実に設定renderThread
する表示手段を提供します。Pixbuf
Image
main = do
initGUI
window <- windowNew
image <- imageNew
set window [containerChild := image]
onDestroy window mainQuit
widgetShowAll window
forkIO . renderThread $ postGUIAsync . imageSetFromPixbuf image
mainGUI
スレッド化されたコンパイルおよびランタイム フラグ
ほとんどの Repa プログラムと同様に、これは次のようにコンパイルする必要があります (-fllvm
お持ちの場合)。
-Odph -rtsopts -threaded -fno-liberate-case -funfolding-use-threshold1000 -funfolding-keeness-factor1000 -optlo-O3
実行時に、次のフラグを渡す必要があります
+RTS -N