JuicyPixels-stbir-0.1.0.0: src/Codec/Picture/STBIR.chs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleContexts #-}
module Codec.Picture.STBIR
( -- * Resize function
resize
-- * Options
, Options(..)
, defaultOptions
, Flag
, flag_ALPHA_PREMULTIPLIED
, flag_ALPHA_USES_COLORSPACE
, Edge(..)
, Filter(..)
, Colorspace(..)
, Scale(..)
, Region(..)
-- * Supported pixel types
, STBIRPixel(..)
, STBIRComponent(..)
, Datatype(..)
) where
import Foreign
import Foreign.C
import Codec.Picture
import Codec.Picture.Types
import qualified Data.Vector.Storable as V
import System.IO.Unsafe (unsafePerformIO)
import Data.Default.Class (Default(..))
#include "stb_image_resize.h"
newtype Flag = Flag { fromFlag :: CInt }
deriving (Eq, Ord, Show, Read)
{- |
Set this flag if your texture has premultiplied alpha. Otherwise, `stbir` will
use alpha-weighted resampling (effectively premultiplying, resampling,
then unpremultiplying).
-}
flag_ALPHA_PREMULTIPLIED :: Flag
flag_ALPHA_PREMULTIPLIED = Flag 1 -- c2hs const has problems due to <<
{- |
The specified alpha channel should be handled as gamma-corrected value even
when doing sRGB operations.
-}
flag_ALPHA_USES_COLORSPACE :: Flag
flag_ALPHA_USES_COLORSPACE = Flag 2
{#enum stbir_edge as Edge {}
with prefix = "STBIR_"
deriving (Eq, Ord, Show, Read, Bounded)
#}
data Filter
= FILTER_DEFAULT -- ^ use same filter type that easy-to-use API chooses
| FILTER_BOX -- ^ A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios
| FILTER_TRIANGLE -- ^ On upsampling, produces same results as bilinear texture filtering
| FILTER_CUBICBSPLINE -- ^ The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque
| FILTER_CATMULLROM -- ^ An interpolating cubic spline
| FILTER_MITCHELL -- ^ Mitchell-Netrevalli filter with B=1\/3, C=1\/3
deriving (Eq, Ord, Show, Read, Enum, Bounded)
-- the separate types here are because c2hs doesn't let me insert haddocks
{#enum stbir_filter as C_Filter {}
deriving (Eq, Ord, Show, Read, Bounded)
#}
cFilter :: Filter -> CInt
cFilter FILTER_DEFAULT = fromIntegral $ fromEnum STBIR_FILTER_DEFAULT
cFilter FILTER_BOX = fromIntegral $ fromEnum STBIR_FILTER_BOX
cFilter FILTER_TRIANGLE = fromIntegral $ fromEnum STBIR_FILTER_TRIANGLE
cFilter FILTER_CUBICBSPLINE = fromIntegral $ fromEnum STBIR_FILTER_CUBICBSPLINE
cFilter FILTER_CATMULLROM = fromIntegral $ fromEnum STBIR_FILTER_CATMULLROM
cFilter FILTER_MITCHELL = fromIntegral $ fromEnum STBIR_FILTER_MITCHELL
{#enum stbir_colorspace as Colorspace {}
omit (STBIR_MAX_COLORSPACES)
with prefix = "STBIR_"
deriving (Eq, Ord, Show, Read, Bounded)
#}
{#enum stbir_datatype as Datatype {}
omit (STBIR_MAX_TYPES)
with prefix = "STBIR_"
#}
-- | Specify scale explicitly for subpixel correctness
data Scale = Scale
{ x_scale :: Float
, y_scale :: Float
, x_offset :: Float
, y_offset :: Float
} deriving (Eq, Ord, Show, Read)
-- | Specify image source tile using texture coordinates
data Region = Region
{ region_s0 :: Float -- ^ x of top-left corner from 0 to 1
, region_t0 :: Float -- ^ y of top-left corner from 0 to 1
, region_s1 :: Float -- ^ x of bottom-right corner from 0 to 1
, region_t1 :: Float -- ^ y of bottom-right corner from 0 to 1
} deriving (Eq, Ord, Show, Read)
data Options = Options
{ flags :: [Flag]
, edgeModeHorizontal :: Edge
, edgeModeVertical :: Edge
, filterHorizontal :: Filter
, filterVertical :: Filter
, colorspace :: Colorspace
, transform :: Either Scale Region
} deriving (Eq, Ord, Show, Read)
-- | These are the options that correspond to the \"Easy-to-use API\".
defaultOptions :: Options
defaultOptions = Options
{ flags = []
, edgeModeHorizontal = EDGE_CLAMP
, edgeModeVertical = EDGE_CLAMP
, filterHorizontal = FILTER_DEFAULT
, filterVertical = FILTER_DEFAULT
, colorspace = COLORSPACE_LINEAR
, transform = Right $ Region 0 0 1 1
}
instance Default Options where
def = defaultOptions
class STBIRComponent a where
stbirType :: a -> Datatype
instance STBIRComponent Word8 where stbirType _ = TYPE_UINT8
instance STBIRComponent Word16 where stbirType _ = TYPE_UINT16
instance STBIRComponent Word32 where stbirType _ = TYPE_UINT32
instance STBIRComponent Float where stbirType _ = TYPE_FLOAT
noAlpha :: CInt
noAlpha = {#const STBIR_ALPHA_CHANNEL_NONE #}
-- | All types currently covered by JP's 'Pixel' are supported.
class (Pixel a, STBIRComponent (PixelBaseComponent a)) => STBIRPixel a where
alphaIndex :: a -> Maybe Int
instance STBIRPixel PixelRGBA16 where alphaIndex _ = Just 3
instance STBIRPixel PixelRGBA8 where alphaIndex _ = Just 3
instance STBIRPixel PixelCMYK16 where alphaIndex _ = Nothing
instance STBIRPixel PixelCMYK8 where alphaIndex _ = Nothing
instance STBIRPixel PixelYCbCr8 where alphaIndex _ = Nothing
instance STBIRPixel PixelRGBF where alphaIndex _ = Nothing
instance STBIRPixel PixelRGB16 where alphaIndex _ = Nothing
instance STBIRPixel PixelYCbCrK8 where alphaIndex _ = Nothing
instance STBIRPixel PixelRGB8 where alphaIndex _ = Nothing
instance STBIRPixel PixelYA16 where alphaIndex _ = Just 1
instance STBIRPixel PixelYA8 where alphaIndex _ = Just 1
instance STBIRPixel PixelF where alphaIndex _ = Nothing
instance STBIRPixel Pixel32 where alphaIndex _ = Nothing
instance STBIRPixel Pixel16 where alphaIndex _ = Nothing
instance STBIRPixel Pixel8 where alphaIndex _ = Nothing
{#fun stbir_resize_subpixel
{ id `Ptr ()' -- ^ const void *input_pixels
, `CInt' -- ^ int input_w
, `CInt' -- ^ int input_h
, `CInt' -- ^ int input_stride_in_bytes
, id `Ptr ()' -- ^ void *output_pixels
, `CInt' -- ^ int output_w
, `CInt' -- ^ int output_h
, `CInt' -- ^ int output_stride_in_bytes
, `Datatype' -- ^ stbir_datatype datatype
, `CInt' -- ^ int num_channels
, `CInt' -- ^ int alpha_channel
, `CInt' -- ^ int flags
, `Edge' -- ^ stbir_edge edge_mode_horizontal
, `Edge' -- ^ stbir_edge edge_mode_vertical
, cFilter `Filter' -- ^ stbir_filter filter_horizontal
, cFilter `Filter' -- ^ stbir_filter filter_vertical
, `Colorspace' -- ^ stbir_colorspace space
, id `Ptr ()' -- ^ void *alloc_context
, `CFloat' -- ^ float x_scale
, `CFloat' -- ^ float y_scale
, `CFloat' -- ^ float x_offset
, `CFloat' -- ^ float y_offset
} -> `CInt'
#}
{#fun stbir_resize_region
{ id `Ptr ()' -- ^ const void *input_pixels
, `CInt' -- ^ int input_w
, `CInt' -- ^ int input_h
, `CInt' -- ^ int input_stride_in_bytes
, id `Ptr ()' -- ^ void *output_pixels
, `CInt' -- ^ int output_w
, `CInt' -- ^ int output_h
, `CInt' -- ^ int output_stride_in_bytes
, `Datatype' -- ^ stbir_datatype datatype
, `CInt' -- ^ int num_channels
, `CInt' -- ^ int alpha_channel
, `CInt' -- ^ int flags
, `Edge' -- ^ stbir_edge edge_mode_horizontal
, `Edge' -- ^ stbir_edge edge_mode_vertical
, cFilter `Filter' -- ^ stbir_filter filter_horizontal
, cFilter `Filter' -- ^ stbir_filter filter_vertical
, `Colorspace' -- ^ stbir_colorspace space
, id `Ptr ()' -- ^ void *alloc_context
, `CFloat' -- ^ float s0
, `CFloat' -- ^ float t0
, `CFloat' -- ^ float s1
, `CFloat' -- ^ float t1
} -> `CInt'
#}
pixelProperty :: (a -> b) -> Image a -> b
pixelProperty f img = let
getFakePixel :: Image a -> a
getFakePixel = undefined
in f $ getFakePixel img
-- | This function allows access to all \"API levels\" of the C library.
-- Pass 'defaultOptions' to use the easy API, or override whichever options
-- you need.
resize
:: (STBIRPixel a)
=> Options
-> Int -- ^ new width
-> Int -- ^ new height
-> Image a
-> Image a
resize opts w' h' img@(Image w h v) = unsafePerformIO $ do
V.unsafeWith v $ \p -> do
let comps = pixelProperty componentCount img
fp <- mallocForeignPtrArray $ w' * h' * comps
let (cfun, f0, f1, f2, f3) = case transform opts of
Left (Scale a b c d) -> (stbir_resize_subpixel, a, b, c, d)
Right (Region a b c d) -> (stbir_resize_region , a, b, c, d)
res <- withForeignPtr fp $ \p' -> cfun
(castPtr p)
(fromIntegral w)
(fromIntegral h)
0
(castPtr p')
(fromIntegral w')
(fromIntegral h')
0
(pixelProperty (stbirType . pixelOpacity) img)
(fromIntegral comps)
(maybe noAlpha fromIntegral $ pixelProperty alphaIndex img)
(fromIntegral $ foldr (.|.) 0 $ map fromFlag $ flags opts)
(edgeModeHorizontal opts)
(edgeModeVertical opts)
(filterHorizontal opts)
(filterVertical opts)
(colorspace opts)
nullPtr
(realToFrac f0)
(realToFrac f1)
(realToFrac f2)
(realToFrac f3)
if res == 0
then error "Codec.Picture.STBIR.resize returned an error"
else return $ Image w' h' $ V.unsafeFromForeignPtr0 fp $ w' * h' * comps