packages feed

stb-image-0.1: example/viewer.hs

-- |Very simple image viewer using OpenGL\/GLUT, provided as an example\/test for 'Codec.Image.STB'.

module Main where

import Data.Bits

import Graphics.Rendering.OpenGL
import Graphics.UI.GLUT

import Codec.Image.STB

import System.Environment
import System.Exit
import System.IO.Unsafe

import Control.Monad
import Foreign

main = do
  initialDisplayMode $= [ RGBAMode, DoubleBuffered ]

  args <- getArgs
  nonGLUTArgs <- initialize "viewer" args
  
  fname <- case nonGLUTArgs of
    [fn] -> return fn
    _    -> putStrLn "usage: viewer <imagefile.ext>" >> exitFailure
         
  img <- loadImage fname >>= \result -> case result of
    Left err  -> putStrLn err >> exitFailure
    Right img -> return img
    
  withImage img $ \p (x,y) c -> do
    putStrLn $ "\"" ++ fname ++ "\" loaded"
    putStrLn $ "resolution = " ++ show x ++ " x " ++ show y ++ ", " ++ show c ++ " bytes per pixel"  

  createWindow ("viewer - " ++ show fname)

  tex <- withExtendedImage img $ \p (x,y) c -> do
    let (pf,pif) = case c of
      { 1 -> ( Luminance,      Luminance8       ) 
      ; 2 -> ( LuminanceAlpha, Luminance8Alpha8 )
      ; 3 -> ( RGB,            RGB8             )
      ; 4 -> ( RGBA,           RGBA8            )
      }
    let size = TextureSize2D (fromIntegral x) (fromIntegral y) 
        pdata = PixelData pf UnsignedByte p              
    [tex] <- genObjectNames 1 
    textureBinding Texture2D $= Just tex 
    texImage2D Nothing NoProxy 0 pif size 0 pdata
    textureFilter Texture2D $= ((Linear',Nothing),Linear')
    return tex

  displayCallback       $= display img tex
  reshapeCallback       $= Just reshape
  keyboardMouseCallback $= Just keyboard

  drawBuffer $= BackBuffers
  postRedisplay Nothing

  mainLoop
  
-- reshape callback
reshape _ = postRedisplay Nothing
  
-- keyboard callback
keyboard key keyState mod pos = 
  case key of 
    Char '\ESC' -> exitWith ExitSuccess
    _ -> return ()    
  
-- glVertex2d / glTexCoord2d, convenient because the OpenGL vertex / texCoord functions are too polymorphic

vt :: Double -> Double -> IO ()
vt x y = vertex (Vertex2 x y)

tc :: Double -> Double -> IO ()
tc x y = texCoord (TexCoord2 x y)

-- display callback  
display img tex = withImage img $ \_ (xsize,ysize) _ -> do
  clear [ColorBuffer] 

  size@(Size xres yres) <- get windowSize
  viewport $= ( Position 0 0 , size )
  let winaspect = fromIntegral xres  / fromIntegral yres  :: Double
      picaspect = fromIntegral xsize / fromIntegral ysize :: Double
  matrixMode $= Projection >> loadIdentity
  ortho (-1) 1 (-1) 1 (-1) 1
          
  texture Texture2D $= Enabled
  textureBinding Texture2D $= Just tex
  let (x,y) = if picaspect > winaspect then (1,winaspect/picaspect) else (picaspect/winaspect,1)
      r = fromIntegral xsize / fromIntegral (nextPowerOfTwo xsize) 
      b = fromIntegral ysize / fromIntegral (nextPowerOfTwo ysize) 
  renderPrimitive Quads $ do 
    tc 0 0 ; vt (-x) ( y)
    tc r 0 ; vt ( x) ( y)
    tc r b ; vt ( x) (-y)
    tc 0 b ; vt (-x) (-y)
         
  swapBuffers
  
log2 :: Int -> Int
log2 n = case n of
  0 -> -1
  _ -> 1 + log2 (shiftR n 1) 
 
nextPowerOfTwo :: Int -> Int      
nextPowerOfTwo n = 2 ^ ( 1 + log2 (n-1) )
  
-- extend the image to have power-of-two sizes
withExtendedImage :: Image -> (Ptr Word8 -> (Int,Int) -> Int -> IO a) -> IO a
withExtendedImage img action = withImage img $ \p (oldx,oldy) c -> do
  let (newx,newy) = (nextPowerOfTwo oldx, nextPowerOfTwo oldy)
  allocaArray (newx*newy*c) $ \q -> do
    forM_ [0..oldy-1] $ \i -> copyArray 
      (q `advancePtr` (i*c*newx))  -- destination 
      (p `advancePtr` (i*c*oldx))  -- source
      (oldx*c)                     -- number of bytes
    action q (newx,newy) c