module Graphics.GD.ByteString.Lazy ( -- * Types Image, Size, Point, Color, -- * Creating and copying images GD.newImage, GD.copyImage, GD.copyRegion, GD.copyRegionScaled, -- * Memory management GD.withImage, -- * Loading images -- ** JPEG loadJpegFile, loadJpegData, loadJpegByteString, -- ** PNG loadPngFile, loadPngData, loadPngByteString, -- ** GIF loadGifFile, loadGifData, loadGifByteString, -- * Saving images -- ** JPEG saveJpegFile, saveJpegByteString, -- ** PNG savePngFile, savePngByteString, -- ** GIF saveGifFile, saveGifByteString, -- * Getting image information GD.imageSize, -- * Querying GD.getPixel, -- * Manipulating images GD.resizeImage, GD.rotateImage, -- * Drawing GD.fillImage, GD.drawFilledRectangle, GD.drawFilledEllipse, GD.drawLine, GD.drawArc, GD.antiAliased, GD.setPixel, -- * Text GD.useFontConfig, drawString, measureString, drawStringCircle, -- * Colors GD.rgb, GD.rgba, GD.toRGBA ) where import Graphics.GD.Internal (Point,Color,Image,GDImage,CFILE,Size) import qualified Graphics.GD.Internal as GD import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Internal as BI import Control.Monad (liftM,unless) import Foreign (Ptr) import qualified Foreign as F import Foreign (peek) import Foreign.C (CInt) import Foreign.C (peekCAString,peekCAString) -- -- * Loading images -- -- | Load a JPEG image from a file. loadJpegFile :: FilePath -> IO Image loadJpegFile = loadImageFile GD.gdImageCreateFromJpeg -- | Load a JPEG image from a buffer. loadJpegData :: Int -- ^ Buffer size. -> Ptr a -- ^ Buffer with image data. -> IO Image loadJpegData = loadImageData GD.gdImageCreateFromJpegPtr -- | Load a JPEG image from a ByteString loadJpegByteString :: L.ByteString -> IO Image loadJpegByteString = onByteStringData loadJpegData -- | Load a PNG image from a file. loadPngFile :: FilePath -> IO Image loadPngFile = loadImageFile GD.gdImageCreateFromPng -- | Load a PNG image from a buffer. loadPngData :: Int -- ^ Buffer size. -> Ptr a -- ^ Buffer with image data. -> IO Image loadPngData = loadImageData GD.gdImageCreateFromPngPtr -- | Load a PNG image from a ByteString loadPngByteString :: L.ByteString -> IO Image loadPngByteString = onByteStringData loadPngData -- | Load a GIF image from a file. loadGifFile :: FilePath -> IO Image loadGifFile = loadImageFile GD.gdImageCreateFromGif -- | Load a GIF image from a buffer. loadGifData :: Int -- ^ Buffer size. -> Ptr a -- ^ Buffer with image data. -> IO Image loadGifData = loadImageData GD.gdImageCreateFromGifPtr -- | Load a GIF image from a ByteString loadGifByteString :: L.ByteString -> IO Image loadGifByteString = onByteStringData loadGifData loadImageFile :: (Ptr CFILE -> IO (Ptr GDImage)) -> FilePath -> IO Image loadImageFile f file = do p <- F.throwIfNull ("Loading image from " ++ file) $ GD.withCFILE file "rb" f GD.mkImage p loadImageData :: (CInt -> Ptr a -> IO (Ptr GDImage)) -> Int -> Ptr a -> IO Image loadImageData f sz buf = do p <- F.throwIfNull ("Loading image") $ f (fromIntegral sz) buf GD.mkImage p onByteStringData :: (Int -> Ptr a -> IO b) -> L.ByteString -> IO b onByteStringData f bstr = case BI.toForeignPtr (lazyToStrict bstr) of (fptr, start, sz) -> F.withForeignPtr fptr $ \ptr -> f sz (F.plusPtr ptr start) where lazyToStrict :: L.ByteString -> B.ByteString lazyToStrict = foldr1 B.append . L.toChunks -- -- * Saving images -- -- | Save an image as a JPEG file. saveJpegFile :: Int -- ^ quality: 0-95, or negative for default quality. -> FilePath -> Image -> IO () saveJpegFile q = saveImageFile (\p h -> GD.gdImageJpeg p h (fromIntegral q)) -- | Write a JPEG format ByteString of an image. saveJpegByteString :: Int -> Image -> IO L.ByteString saveJpegByteString q = saveImageByteString (\p h -> GD.gdImageJpegPtr p h (fromIntegral q)) -- | Save an image as a PNG file. savePngFile :: FilePath -> Image -> IO () savePngFile = saveImageFile GD.gdImagePng -- | Write a PNG format ByteString of an image. savePngByteString :: Image -> IO L.ByteString savePngByteString = saveImageByteString GD.gdImagePngPtr -- | Save an image as a GIF file. saveGifFile :: FilePath -> Image -> IO () saveGifFile = saveImageFile GD.gdImageGif -- | Write a GIF format ByteString of an image. saveGifByteString :: Image -> IO L.ByteString saveGifByteString = saveImageByteString GD.gdImageGifPtr saveImageFile :: (Ptr GDImage -> Ptr CFILE -> IO ()) -> FilePath -> Image -> IO () saveImageFile f file i = GD.withImagePtr i (\p -> GD.withCFILE file "wb" (f p)) saveImageByteString :: (Ptr GDImage -> Ptr CInt -> IO (Ptr a)) -> Image -> IO (L.ByteString) saveImageByteString f img = GD.withImagePtr img (\p -> dataByteString (f p)) dataByteString :: (Ptr CInt -> IO (Ptr a)) -> IO L.ByteString dataByteString f = F.alloca $ \szPtr -> do datPtr <- f szPtr >>= F.newForeignPtr GD.gdFree . F.castPtr liftM (L.fromChunks . return . BI.fromForeignPtr datPtr 0 . fromIntegral) (peek szPtr) -- -- * Text -- -- | Draw a string using the FreeType 2.x library drawString :: B.ByteString -- ^ Font name -> Double -- ^ Font point size -> Double -- ^ Angle in counterclockwise radians -> Point -- ^ Origin -> B.ByteString -- ^ Text, including HTML entities -> Color -> Image -> IO (Point, Point, Point, Point) -- ^ Bounding box -- of the drawn -- text drawString fontName ptSize angle (oriX, oriY) txt color img = GD.withImagePtr img $ drawStringImagePtr color fontName ptSize angle (oriX, oriY) txt -- | Measure a string using the FreeType 2.x library. This computes -- the bounding box but does not actually draw the string to any -- image. measureString :: B.ByteString -- ^ Font name -> Double -- ^ Font point size -> Double -- ^ Angle in counterclockwise radians -> Point -- ^ Origin -> B.ByteString -- ^ Text, including HTML entities -> Color -> IO (Point, Point, Point, Point) -- ^ Bounding -- box of the -- drawn text measureString fontName ptSize angle (oriX, oriY) txt color = drawStringImagePtr color fontName ptSize angle (oriX, oriY) txt F.nullPtr drawStringImagePtr :: Color -> B.ByteString -> Double -> Double -> Point -> B.ByteString -> Ptr GDImage -> IO (Point, Point, Point, Point) drawStringImagePtr color fontName ptSize angle (oriX, oriY) txt imgPtr = F.allocaArray 8 $ \bboxPtr -> B.useAsCString fontName $ \cFontName -> B.useAsCString txt $ \cTxt -> do res <- GD.gdImageStringFT imgPtr bboxPtr color cFontName (GD.double ptSize) (GD.double angle) (GD.int oriX) (GD.int oriY) cTxt if res == F.nullPtr then F.peekArray 8 bboxPtr >>= parseBBox else peekCAString res >>= ioError . userError where parseBBox l = case map GD.int l of [llx, lly, lrx, lry, urx, ury, ulx, uly] -> return ((llx, lly), (lrx, lry), (urx, ury), (ulx, uly)) _ -> ioError $ userError $ "parseBBox with /= 8 elements: " ++ show l -- | Draw strings around the top and bottom of a torus drawStringCircle :: Point -- ^ Center of text path circle -> Double -- ^ Outer radius of text -> Double -- ^ Fraction of radius occupied by text -> Double -- ^ Portion of circle arc filled by text -> B.ByteString -- ^ Font name -> Double -- ^ Font size hint -> B.ByteString -- ^ Text to write on the top of the circle -> B.ByteString -- ^ Text to write on the bottom of the circle -> Color -- ^ Text color -> Image -> IO () drawStringCircle (ctrX, ctrY) rad textRad textFill fontName fontSize topTxt bottomTxt color img = B.useAsCString fontName $ \cFontName -> B.useAsCString topTxt $ \cTopTxt -> B.useAsCString bottomTxt $ \cBottomTxt -> GD.withImagePtr img $ \imgPtr -> do res <- GD.gdImageStringFTCircle imgPtr (GD.int ctrX) (GD.int ctrY) (GD.double rad) (GD.double textRad) (GD.double textFill) cFontName (GD.double fontSize) cTopTxt cBottomTxt color unless (res == F.nullPtr) (peekCAString res >>= ioError . userError)