在 SDL2 中貼圖

jaiyalas

Mar/16/2016

前言

Simple DirectMedia Layer

SDL, is a library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.

  • Surface

    • is used in software rendering

    • using regular RAM to store image data

    • using CPU

  • Texture

    • is used in hardware rendering

    • using regular VRAM to store image data

    • using GPU (internally, using OpenGL)

有兩套 haskell bindings to SDL 2 library,

  • SDL
    • Francesco Ariis
    • Downloads: 9165 total
  • sdl2
    • Gabríel Arthúr Pétursson and Oliver Charles
    • Downloads: 5520 total

關於讀圖, sdl2 只提供了

SDL_Surface* SDL_LoadBMP(const char* file)

也就是說,我們基本上只有

loadBMP :: MonadIO m => FilePath -> m Surface

我的問題

  • 我不想要(只能)用 BMP 呀!
  • 我不想要(只能)用 Surface 呀!

我的計畫

  • Plan A 找人家寫好的 Library
  • Plan B 自己讀圖, 自己畫

A 計畫

  1. SDL_image 2.0 (C library)

    • SDL-image (Haskell package)

      • depends on that SDL
  2. JuixyPixel (Haskell package)

    • 自己的格式: Image

    • 沒人寫過 JuixyPixel -> sdl2 (至少我沒找到過)

放棄!!

B 計畫

今天第一個題目

使用 sdl2, sdl2-cairo, cairo 和 JuicyPixels

來讀/繪圖的崩潰歷程

先來整理一下各套件是怎麼儲存圖片的..

JuicyPixels

import qualified Data.Vector.Storable as V

data Image a = Image
  { imageWidth  :: {-# UNPACK #-} !Int
  , imageHeight :: {-# UNPACK #-} !Int
  , imageData   :: V.Vector (PixelBaseComponent a)}

e.g. V.Vector PixelRGBA8 or V.Vector PixelCMYK16

SDL2 (Texture)

newtype Texture =
    Texture Raw.Texture deriving (Eq, Typeable)

type Texture = Ptr ()

.....(默)

SDL2 (Surface)

import qualified Data.Vector.Storable.Mutable as MSV

data Surface = Surface
    (Ptr Raw.Surface) (Maybe (MSV.IOVector Word8))

data Surface = Surface
  { ...
  , surfacePixels :: !(Ptr ())
  , ...} deriving (Eq, Show, Typeable)
  • MSV.IOVector Word8 ?
  • surfacePixels :: !(Ptr ()) ?

放棄 Vector-to-Vector 暴力硬塞法

我自己一個像素一個像素畫總行了吧?! (ㄋ羞)

在 sdl2 裡面繪圖要用 Renderer

搞不清楚 Renderer, Surface, Texture 仨之間的關係

聽說 SDL.Cairo.Canvas 比較方便繪圖
改用 sdl2-cairo

Canvas 可以...

data Canvas a = ...
Instances
    Functor Canvas
    Applicative Canvas
    Monad Canvas
    MonadIO Canvas
... = do
  background $ gray 102
  fill $ red 255 !@ 128
  noStroke
  rect $ D 200 200 100 100
  ...

但是

wrapper around the Cairo Render monad, providing a Processing-style API.

那我何必呢? 直接用 Cairo 的 Render 就好啦~ orz

乾脆兩種都做:

class Pixel px => RenderablePixel px where
    drawPx :: (V2 Int, px) -> Canvas ()

class Pixel px => RenderablePixelC px where
    renderPx :: (V2 Int, px) -> Render ()

連結: sdl2-cairo-image, Render.hs

Then..

  • lots of bug..
  • Vector-to-Vector
  • rendering with Renderer

今天第二個題目

沒有 depends on sdl2SDL_image binding 是吧?!

我自己寫!

+--- Image.hs
|--- Image
     +--- General.hs
     |--- Info.hs
     |--- Loading.hs
     |--- Raw.hs
     |--- Raw
          +--- General.hs
          |--- Info.hs
          |--- Loading.hs
          |--- Enum.hsc

Step 1

設定 cabal (以 sdl2-image 為例)

includes:
  SDL_image.h
extra-libraries:
  SDL2_image
pkgconfig-depends:
  SDL2_image >= 2.0.0

Step 2

  • 建立 .hsc 檔案來描述 Haskell 對 C 介面
  • .hsc 裡面支援很多指令來直接和 C 做溝通
    • #include
    • #const
    • #type
  • 可以都用來描述 常數結構 等等
  • 通常塞在檔案結構的很裡面(.../Raw/ 下面)

範例: 常數

module SDL.Image.Raw.Enum where
--
#include "SDL_image.h"
--
pattern IMG_INIT_JPG = (#const IMG_INIT_JPG) :: CInt
pattern IMG_INIT_PNG = (#const IMG_INIT_PNG) :: CInt
pattern IMG_INIT_TIF = (#const IMG_INIT_TIF) :: CInt

範例: 常數 (compiled)

module SDL.Image.Raw.Enum where
--
{-# LINE 13 "src/SDL/Image/Raw/Enum.hsc" #-}
--
pattern IMG_INIT_JPG = (1) :: CInt
{-# LINE 19 "src/SDL/Image/Raw/Enum.hsc" #-}
pattern IMG_INIT_PNG = (2) :: CInt
{-# LINE 20 "src/SDL/Image/Raw/Enum.hsc" #-}
pattern IMG_INIT_TIF = (4) :: CInt
{-# LINE 21 "src/SDL/Image/Raw/Enum.hsc" #-}

範例: 簡單結構

type Texture = Ptr ()
type InitFlag = Word32
type Keycode = (#type SDL_Keycode)

範例: 複雜結構

data Color = Color !Word8 !Word8 !Word8 !Word8
           deriving (Eq, Show, Typeable)

instance Storable Color where
  sizeOf _ = (#size SDL_Color)
  peek ptr = do
    r <- (#peek SDL_Color, r) ptr
    g <- (#peek SDL_Color, g) ptr
    b <- (#peek SDL_Color, b) ptr
    a <- (#peek SDL_Color, a) ptr
    return $! Color r g b a

Step 3

  • 把 C 的 module 變成 ../Raw/xxx.hs
 |--- Raw
      +--- General.hs
      |--- Info.hs
      |--- Loading.hs
      |--- Enum.hsc

low-level binding

  • foreign import ccall

  • 注意 function type!

SDL_Surface *IMG_LoadTyped_RW( SDL_RWops *src
                             , int freesrc
                             , char *type)
foreign import ccall "SDL_image.h IMG_LoadTyped_RW"
   imgLoadRWTyped_FFI :: Ptr RowType.RWops
                      -> CInt
                      -> CString
                      -> IO (Ptr RowType.Surface)

less-low-level binding

  • 可以根據情況再稍微多包一點點

  • 讓 type 更有意義一點

imgLoadRWTyped :: MonadIO m => Ptr RowType.RWops
               -> Bool
               -> CString
               -> m (Ptr RowType.Surface)
imgLoadRWTyped p autoFree typeStr = liftIO $
   imgLoadRWTyped_FFI p (fromBool autoFree) typeStr
{-# INLINE imgLoadRWTyped #-}

Step 4

  • 設計好用的 data structurestype-classes
  • low-level to high-level, e.g.
imgLoadRWTyped :: MonadIO m => Ptr SDL.Row.RWops
               -> Bool
               -> CString
               -> m (Ptr SDL.Row.Surface)
surfaceFromImgT :: MonadIO m => ImageType -> Image -> m SDL.Surface

Then..