packages feed

diagrams-rasterific (empty) → 0.1

raw patch · 16 files changed

+921/−0 lines, 16 filesdep +FontyFruitydep +JuicyPixelsdep +Rasterificsetup-changedbinary-added

Dependencies added: FontyFruity, JuicyPixels, Rasterific, base, bytestring, containers, data-default-class, diagrams-core, diagrams-lib, directory, filepath, lens, mtl, old-time, optparse-applicative, process, split, statestack, time, unix

Files

+ LICENSE view
@@ -0,0 +1,34 @@+Copyright (c) 2014 diagrams-rasterific team:++   Vincent Berthoux <vincent.berthoux@gmail.com>+   Jeffrey Rosenbluth <jeffrey.rosenbluth@gmail.com>+   Brent Yorgey <byorgey@ciss.upenn.edu>++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Jeffrey Rosenbluth nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,10 @@+diagrams-rasterific+===================++_diagrams-rasterific_ is a an Rasterific backend for [diagrams]. Diagrams is a powerful,+flexible, declarative domain-specific language for creating vector graphics,+using the [Haskell programming language][haskell]. It supports png, tif, bmp,+jpg and animated GIF output.++[diagrams]: http://projects.haskell.org/diagrams/+[haskell]: http://www.haskell.org/haskellwiki/Haskell
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ diagrams-rasterific.cabal view
@@ -0,0 +1,50 @@+name:                diagrams-rasterific+version:             0.1+synopsis:            Rasterific backend for diagrams.+description:         A full-featured backend for rendering+                     diagrams using the Rasterific rendering engine.+homepage:             http://projects.haskell.org/diagrams/+license:             BSD3+license-file:        LICENSE+author:              Jeffrey Rosenbluth+maintainer:          diagrams-discuss@googlegroups.com+bug-reports:         http://github.com/diagrams/diagrams-cairo/issues+category:            Graphics+build-type:          Simple+data-files:          fonts/*.ttf+extra-source-files:  README.md+cabal-version:       >=1.10+Source-repository head+  type:     git+  location: http://github.com/diagrams/diagrams-rasterific.git++library+  exposed-modules:     Diagrams.Backend.Rasterific+                       Diagrams.Backend.Rasterific.CmdLine+  hs-source-dirs:      src+  other-modules:       Paths_diagrams_rasterific+  build-depends:       base >= 4.2 && < 4.8,+                       old-time,+                       time,+                       process,+                       directory,+                       diagrams-core >= 1.2 && < 1.3,+                       diagrams-lib >= 1.2 && < 1.3,+                       Rasterific >= 0.2 && < 0.4,+                       FontyFruity >= 0.2 && < 0.4,+                       JuicyPixels >= 3.1.5 && < 3.2,+                       lens >= 4.0 && < 4.3,+                       mtl >= 2.1 && < 2.3,+                       statestack >= 0.2 && < 0.3,+                       data-default-class >= 0.0 && < 0.1,+                       containers >= 0.5 && < 0.6,+                       filepath >= 1.2 && < 1.4,+                       optparse-applicative >= 0.7 && < 0.10,+                       bytestring >= 0.9 && < 0.11,+                       split >= 0.1 && < 1.3++  if !os(windows)+    cpp-options:       -DCMDLINELOOP+    Build-depends:     unix >= 2.4 && < 2.8++  default-language:    Haskell2010
+ fonts/OpenSans-Bold.ttf view

binary file changed (absent → 224592 bytes)

+ fonts/OpenSans-BoldItalic.ttf view

binary file changed (absent → 213292 bytes)

+ fonts/OpenSans-ExtraBold.ttf view

binary file changed (absent → 222584 bytes)

+ fonts/OpenSans-ExtraBoldItalic.ttf view

binary file changed (absent → 213420 bytes)

+ fonts/OpenSans-Italic.ttf view

binary file changed (absent → 212896 bytes)

+ fonts/OpenSans-Light.ttf view

binary file changed (absent → 222412 bytes)

+ fonts/OpenSans-LightItalic.ttf view

binary file changed (absent → 213128 bytes)

+ fonts/OpenSans-Regular.ttf view

binary file changed (absent → 217360 bytes)

+ fonts/OpenSans-Semibold.ttf view

binary file changed (absent → 221328 bytes)

+ fonts/OpenSans-SemiboldItalic.ttf view

binary file changed (absent → 212820 bytes)

+ src/Diagrams/Backend/Rasterific.hs view
@@ -0,0 +1,475 @@+{-# LANGUAGE DeriveDataTypeable        #-}+{-# LANGUAGE DeriveGeneric             #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts          #-}+{-# LANGUAGE FlexibleInstances         #-}+{-# LANGUAGE GADTs                     #-}+{-# LANGUAGE MultiParamTypeClasses     #-}+{-# LANGUAGE TemplateHaskell           #-}+{-# LANGUAGE TypeFamilies              #-}+{-# LANGUAGE TypeSynonymInstances      #-}+{-# LANGUAGE ViewPatterns              #-}++-------------------------------------------------------------------------------+-- |+-- Module      :  Diagrams.Backend.Rasterific+-- Copyright   :  (c) 2014 diagrams-rasterific team (see LICENSE)+-- License     :  BSD-style (see LICENSE)+-- Maintainer  :  diagrams-discuss@googlegroups.com+--+-- A full-featured rendering backend for diagrams using Rasterific,+-- implemented natively in Haskell (making it easy to use on any+-- platform). Can create png, tif, bmp, jpg, and animated GIFs.+--+-- To invoke the Rasterific backend, you have three options.+--+-- * You can use the "Diagrams.Backend.Rasterific.CmdLine" module to create+--   standalone executables which output images when invoked.+--+-- * You can use the 'renderRasterific' function provided by this module,+--   which gives you more flexible programmatic control over when and+--   how images are output (making it easy to, for example, write a+--   single program that outputs multiple images, or one that outputs+--   images dynamically based on user input, and so on).+--+-- * For the most flexibility (/e.g./ if you want access to the+--   resulting Rasterific value directly in memory without writing it to+--   disk), you can manually invoke the 'renderDia' method from the+--   'Diagrams.Core.Types.Backend' instance for @Rasterific@.  In particular,+--   'Diagrams.Core.Types.renderDia' has the generic type+--+-- > renderDia :: b -> Options b v -> QDiagram b v m -> Result b v+--+-- (omitting a few type class constraints).  @b@ represents the+-- backend type, @v@ the vector space, and @m@ the type of monoidal+-- query annotations on the diagram.  'Options' and 'Result' are+-- associated data and type families, respectively, which yield the+-- type of option records and rendering results specific to any+-- particular backend.  For @b ~ Rasterific@ and @v ~ R2@, we have+--+-- > data Options Rasterific R2 = RasterificOptions+-- >          { _rasterificSizeSpec      :: SizeSpec2D -- ^ The requested size of the output+-- >          , _rasterificBypassAdjust  :: Bool       -- ^ Should the 'adjustDia' step be bypassed during rendering?+-- >          }+--+-- @+-- data family Render Rasterific R2 = 'R (RenderM ())'+-- @+--+-- @+-- type family Result Rasterific R2 = 'Image PixelRGBA8'+-- @+--+-- So the type of 'renderDia' resolves to+--+-- @+-- renderDia :: Rasterific -> Options Rasterific R2 -> QDiagram Rasterific R2 m -> 'Image PixelRGBA8'+-- @+--+-- which you could call like @renderDia Rasterific (RasterificOptions (Width 250))+-- myDiagram@.+--+-------------------------------------------------------------------------------+module Diagrams.Backend.Rasterific+  ( Rasterific(..)+  , B -- rendering token+  , Options(..)++  , renderRasterific+  , size++  , writeJpeg++  ) where++import           Diagrams.Core.Compile+import           Diagrams.Core.Transform     (matrixHomRep)+++import           Diagrams.Prelude            hiding (opacity, view)+import           Diagrams.TwoD.Adjust        (adjustDia2D)+import           Diagrams.TwoD.Attributes    (splitTextureFills)+import           Diagrams.TwoD.Path          (Clip (Clip), getFillRule)+import           Diagrams.TwoD.Size          (sizePair)+import           Diagrams.TwoD.Text          hiding (Font)++import           Codec.Picture+import           Codec.Picture.Types         (dropTransparency, convertPixel+                                             ,promoteImage, convertImage)++import           GHC.Float                   (double2Float, float2Double)++import qualified Graphics.Rasterific         as R+import           Graphics.Rasterific.Texture (uniformTexture, Gradient+                                             ,linearGradientTexture ,withSampler+                                             ,radialGradientWithFocusTexture+                                             ,transformTexture)++import qualified Graphics.Rasterific.Transformations as R++import           Graphics.Text.TrueType      (loadFontFile, Font, stringBoundingBox)+++import           Control.Lens                hiding (transform, ( # ))+import           Control.Monad               (when)+import           Control.Monad.StateStack+import           Control.Monad.Trans         (lift)+++import qualified Data.ByteString.Lazy as L   (writeFile)+import           Data.Default.Class+import qualified Data.Foldable               as F+import           Data.Maybe                  (fromMaybe, isJust, fromJust)+import           Data.Tree+import           Data.Typeable+import           Data.Word                   (Word8)++import           System.FilePath             (takeExtension)+import           System.IO.Unsafe            (unsafePerformIO)+import           Paths_diagrams_rasterific   (getDataFileName)++--------------------------------------------------------------------------------+-- | This data declaration is simply used as a token to distinguish+--   the Rasterific backend: (1) when calling functions where the type+--   inference engine would otherwise have no way to know which+--   backend you wanted to use, and (2) as an argument to the+--   'Backend' and 'Renderable' type classes.+data Rasterific = Rasterific+  deriving (Eq,Ord,Read,Show,Typeable)++type B = Rasterific++data RasterificState =+  RasterificState { _accumStyle :: Style R2+                    -- ^ The current accumulated style.+                  }++makeLenses ''RasterificState++instance Default RasterificState where+  def = RasterificState+        { _accumStyle       = mempty+        }++-- | The custom monad in which intermediate drawing options take+--   place; 'Graphics.Rasterific.Drawing' is Rasterific's own rendering+--   monad.+type RenderM a = StateStackT RasterificState RenderR a++type RenderR = R.Drawing PixelRGBA8++liftR :: RenderR a -> RenderM a+liftR = lift++runRenderM :: RenderM a -> RenderR a+runRenderM = flip evalStateStackT def++-- From Diagrams.Core.Types.+instance Backend Rasterific R2 where+  data Render  Rasterific R2 = R (RenderM ())+  type Result  Rasterific R2 = Image PixelRGBA8+  data Options Rasterific R2 = RasterificOptions+          { _size      :: SizeSpec2D -- ^ The requested size of the output+          }+    deriving (Show)++  renderRTree _ opts t =+    R.renderDrawing (round w) (round h) bgColor r+    where+      r = runRenderM . runR . toRender $ t+      (w,h) = sizePair (opts^.size)+      bgColor = PixelRGBA8 0 0 0 0++  adjustDia c opts d = adjustDia2D size c opts (d # reflectY # fontSizeO 12)++toRender :: RTree Rasterific R2 a -> Render Rasterific R2+toRender = fromRTree+  . Node (RStyle (mempty # recommendFillColor (transparent :: AlphaColour Double)))+  . (:[])+  . splitTextureFills+    where+      fromRTree (Node (RPrim p) _) = render Rasterific p+      fromRTree (Node (RStyle sty) rs) = R $ do+        save+        accumStyle %= (<> sty)+        aStyle <- use accumStyle+        let R r = F.foldMap fromRTree rs+            m = evalStateStackT r (RasterificState aStyle)+        clip sty m+        restore+      fromRTree (Node _ rs) = F.foldMap fromRTree rs++runR :: Render Rasterific R2 -> RenderM ()+runR (R r) = r++instance Monoid (Render Rasterific R2) where+  mempty  = R $ return ()+  (R rd1) `mappend` (R rd2) = R (rd1 >> rd2)++size :: Lens' (Options Rasterific R2) SizeSpec2D+size = lens (\(RasterificOptions {_size = s}) -> s)+                     (\o s -> o {_size = s})++rasterificStrokeStyle :: Style v+                     -> (Float, R.Join, (R.Cap, R.Cap), Maybe (R.DashPattern, Float))+rasterificStrokeStyle s = (strokeWidth, strokeJoin, strokeCaps, strokeDash)+  where+    strokeWidth = double2Float $ case getLineWidth <$> getAttr s of+                      Just o ->  fromOutput o+                      _               ->  1+    strokeJoin = fromMaybe (R.JoinMiter 0) (fromLineJoin . getLineJoin <$> getAttr s)+    strokeCaps = (strokeCap, strokeCap)+    strokeCap = fromMaybe (R.CapStraight 0) (fromLineCap . getLineCap <$> getAttr s)+    strokeDash = fromDashing . getDashing <$> getAttr s++fromLineCap :: LineCap -> R.Cap+fromLineCap LineCapButt   = R.CapStraight 0+fromLineCap LineCapRound  = R.CapRound+fromLineCap LineCapSquare = R.CapStraight 1++fromLineJoin :: LineJoin -> R.Join+fromLineJoin LineJoinMiter = R.JoinMiter 0+fromLineJoin LineJoinRound = R.JoinRound+fromLineJoin LineJoinBevel = R.JoinMiter 1++fromDashing :: Dashing -> (R.DashPattern, Float)+fromDashing (Dashing ds d) = (map double2Float ds', double2Float d')+  where+    ds' = map fromOutput ds+    d' = fromOutput d++fromFillRule :: FillRule -> R.FillMethod+fromFillRule EvenOdd = R.FillEvenOdd+fromFillRule _ = R.FillWinding++-- | Get an accumulated style attribute from the render monad state.+getStyleAttrib :: AttributeClass a => (a -> b) -> RenderM (Maybe b)+getStyleAttrib f = (fmap f . getAttr) <$> use accumStyle++rasterificColor :: SomeColor -> Double -> PixelRGBA8+rasterificColor c o = PixelRGBA8 r g b a+  where+    (r, g, b, a) = (int r', int g', int b', int (o * a'))+    (r', g', b', a') = colorToSRGBA (toAlphaColour c)+    int x = round (255 * x)++rasterificSpreadMethod :: SpreadMethod -> R.SamplerRepeat+rasterificSpreadMethod GradPad      = R.SamplerPad+rasterificSpreadMethod GradReflect  = R.SamplerReflect+rasterificSpreadMethod GradRepeat   = R.SamplerRepeat++rasterificStops :: [GradientStop] -> Gradient PixelRGBA8+rasterificStops s = map fromStop s+  where+    fromStop (GradientStop c v) =+      (double2Float v, rasterificColor c 1)++rasterificLinearGradient :: LGradient -> R.Texture PixelRGBA8+rasterificLinearGradient g = transformTexture tr tx+  where+    tr = rasterificMatTransf (inv $ g^.lGradTrans)+    tx = withSampler spreadMethod (linearGradientTexture gradDef p0 p1)+    spreadMethod = rasterificSpreadMethod (g^.lGradSpreadMethod)+    gradDef = rasterificStops (g^.lGradStops)+    p0 = p2v2 (g^.lGradStart)+    p1 = p2v2 (g^.lGradEnd)+++rasterificRadialGradient :: RGradient -> R.Texture PixelRGBA8+rasterificRadialGradient g = transformTexture tr tx+  where+    tr = rasterificMatTransf (inv $ g^.rGradTrans)+    tx = withSampler spreadMethod (radialGradientWithFocusTexture gradDef c r f)+    spreadMethod = rasterificSpreadMethod (g^.rGradSpreadMethod)+    c = p2v2 (g^.rGradCenter1)+    f = p2v2 (g^.rGradCenter0)+    r = double2Float r1+    gradDef = rasterificStops ss++    -- Adjust the stops so that the gradient begins at the perimeter of+    -- the inner circle (center0, radius0) and ends at the outer circle.+    r0 = g^.rGradRadius0+    r1 = g^.rGradRadius1+    stopFracs = r0 / r1 : map (\s -> (r0 + (s^.stopFraction) * (r1-r0)) / r1)+                (g^.rGradStops)+    gradStops = case g^.rGradStops of+      []       -> []+      xs@(x:_) -> x : xs+    ss = zipWith (\gs sf -> gs & stopFraction .~ sf ) gradStops stopFracs++-- Convert a diagrams @Texture@ and opacity to a rasterific texture.+rasterificTexture :: Texture -> Double -> R.Texture PixelRGBA8+rasterificTexture (SC c) o = uniformTexture $ rasterificColor c o+rasterificTexture (LG g) _ = rasterificLinearGradient g+rasterificTexture (RG g) _ = rasterificRadialGradient g++v2 :: Double -> Double -> R.Point+v2 x y = R.V2 x' y'+  where+    (x', y') = (double2Float x, double2Float y)++p2v2 :: P2 -> R.Point+p2v2 p = uncurry v2 $ unp2 p++r2v2 :: R2 -> R.Point+r2v2 r = uncurry v2 $ unr2 r++rasterificPtTransf :: T2 -> R.Point -> R.Point+rasterificPtTransf tr p =  p2v2 $ transform tr p'+  where+    p' = mkP2 (float2Double x) (float2Double y)+    R.V2 x y = p++rasterificMatTransf :: T2 -> R.Transformation+rasterificMatTransf tr = R.Transformation a c e b d f+  where+    [[a, b], [c, d], [e, f]] = (map . map) double2Float (matrixHomRep tr)++-- Note: Using view patterns confuses ghc to think there are missing patterns,+-- so we avoid them here.+renderSeg :: Located (Segment Closed R2) -> R.Primitive+renderSeg l =+  case viewLoc l of+    (p, (Linear (OffsetClosed v))) ->+      R.LinePrim $ R.Line p' (p' + r2v2 v)+      where+        p' = p2v2 p+    (p, (Cubic u1 u2 (OffsetClosed u3))) ->+      R.CubicBezierPrim $ R.CubicBezier q0 q1 q2 q3+      where+        (q0, q1, q2, q3) = (p2v2 p, q0 + r2v2 u1, q0 + r2v2 u2, q0 + r2v2 u3)++renderPath :: Path R2 -> [[R.Primitive]]+renderPath p = (map . map) renderSeg (pathLocSegments p)++clip :: Style v -> RenderR () -> RenderM ()+clip sty d =+  maybe (liftR d)+        (\paths -> liftR $ R.withClipping+                  (R.fill (concat . concat $ (map renderPath paths))) d)+        (op Clip <$> getAttr sty)++-- Stroke both dashed and solid lines.+mkStroke :: Float ->  R.Join -> (R.Cap, R.Cap) ->  Maybe (R.DashPattern, Float)+      -> [[R.Primitive]] -> RenderR ()+mkStroke l j c d  primList =+  maybe (mapM_ (R.stroke l j c) primList)+        (\(dsh, off) -> mapM_ (R.dashedStrokeWithOffset off dsh l j c) primList)+        d++instance Renderable (Path R2) Rasterific where+  render _ p = R $ do+    f <- getStyleAttrib getFillTexture+    s <- fromMaybe (SC (SomeColor (black :: Colour Double))) +         <$> getStyleAttrib getLineTexture+    o <- fromMaybe 1 <$> getStyleAttrib getOpacity+    r <- fromMaybe Winding <$> getStyleAttrib getFillRule+    sty <- use accumStyle++    let (l, j, c, d) = rasterificStrokeStyle sty+        rule = fromFillRule r++        -- For stroking we need to keep all of the contours separate.+        primList = renderPath p++        -- For filling we need to concatenate them into a flat list.+        prms = concat primList++    when (isJust f) $ liftR (R.withTexture (rasterificTexture (fromJust f) o)+                    $ R.fillWithMethod rule prms)+    liftR (R.withTexture (rasterificTexture s o) $ mkStroke l j c d primList)++-- read only of static data (safe)+ro :: FilePath -> FilePath+ro = unsafePerformIO . getDataFileName++openSansRegular :: Font+openSansRegular = fnt+  where+    Right fnt = unsafePerformIO . loadFontFile $ (ro "fonts/OpenSans-Regular.ttf")++openSansBold :: Font+openSansBold = fnt+  where+    Right fnt = unsafePerformIO . loadFontFile $ (ro "fonts/OpenSans-Bold.ttf")++openSansItalic :: Font+openSansItalic = fnt+  where+    Right fnt = unsafePerformIO . loadFontFile $ (ro "fonts/OpenSans-Italic.ttf")++openSansBoldItalic :: Font+openSansBoldItalic = fnt+  where+    Right fnt = unsafePerformIO . loadFontFile $ (ro "fonts/OpenSans-BoldItalic.ttf")++fromFontStyle :: FontSlant -> FontWeight -> Font+fromFontStyle FontSlantItalic FontWeightBold = openSansBoldItalic+fromFontStyle FontSlantOblique FontWeightBold = openSansBoldItalic+fromFontStyle FontSlantNormal FontWeightBold = openSansBold+fromFontStyle FontSlantItalic FontWeightNormal = openSansItalic+fromFontStyle FontSlantOblique FontWeightNormal = openSansItalic+fromFontStyle _ _ = openSansRegular++textBox :: Font -> Int -> String -> (Double, Double)+textBox f ps str = (float2Double w, float2Double h)+  where+    (w, h) = stringBoundingBox f 96 ps str++instance Renderable Text Rasterific where+  render _ (Text tt tn al str) = R $ do+    isLocal <- fromMaybe True <$> getStyleAttrib getFontSizeIsLocal+    fs      <- fromMaybe 12 <$> getStyleAttrib (fromOutput . getFontSize)+    slant   <- fromMaybe FontSlantNormal <$> getStyleAttrib getFontSlant+    fw      <- fromMaybe FontWeightNormal <$> getStyleAttrib getFontWeight+    f       <- fromMaybe (SC (SomeColor (black :: Colour Double))) +               <$> getStyleAttrib getFillTexture+    o       <- fromMaybe 1 <$> getStyleAttrib getOpacity+    let fColor = rasterificTexture f o+        fs' = round fs+        fnt = fromFontStyle slant fw+        (x, y) = textBox fnt fs' str+        (refX, refY) = case al of+          BaselineText -> (0, y)+          BoxAlignedText xt yt -> (x * xt, (1 - yt) * y)+        tr = if isLocal then tt else tn+        p = rasterificPtTransf ((moveOriginBy (r2 (refX, refY)) mempty)) (R.V2 0 0)+    liftR (R.withTransformation (rasterificMatTransf (tr <> reflectionY))+          (R.withTexture fColor $ R.printTextAt fnt fs' p str))++toImageRGBA8 :: DynamicImage -> Image PixelRGBA8+toImageRGBA8 (ImageRGBA8 i)  = i+toImageRGBA8 (ImageRGB8 i)   = promoteImage i+toImageRGBA8 (ImageYCbCr8 i) = promoteImage $ (convertImage i :: Image PixelRGB8)+toImageRGBA8 (ImageY8 i)     = promoteImage i+toImageRGBA8 (ImageYA8 i)    = promoteImage i+toImageRGBA8 (ImageCMYK8 i)  = promoteImage $ (convertImage i :: Image PixelRGB8)+toImageRGBA8 _               = error "Unsupported Pixel type"++instance Renderable (DImage Embedded) Rasterific where+  render _ (DImage iD w h tr) = R $ liftR+                               (R.withTransformation+                               (rasterificMatTransf (tr <> reflectionY))+                               (R.drawImage img 0 p))+    where+      ImageRaster dImg = iD+      img = toImageRGBA8 dImg+      trl = moveOriginBy (r2 ((fromIntegral w / 2), (fromIntegral h / 2))) mempty +      p = rasterificPtTransf trl (R.V2 0 0)++writeJpeg :: Word8 -> FilePath -> Result Rasterific R2 -> IO ()+writeJpeg quality outFile img = L.writeFile outFile bs+  where+    bs = encodeJpegAtQuality quality (pixelMap (convertPixel . dropTransparency) img)++renderRasterific :: FilePath -> SizeSpec2D -> Word8 -> Diagram Rasterific R2 -> IO ()+renderRasterific outFile sizeSpec quality d = writer outFile img+  where+    writer = case takeExtension outFile of+              ".png" -> writePng+              ".tif" -> writeTiff+              ".bmp" -> writeBitmap+              ".jpg" -> writeJpeg q+              _      -> writePng+    img = renderDia Rasterific (RasterificOptions sizeSpec) d+    q = max quality 100
+ src/Diagrams/Backend/Rasterific/CmdLine.hs view
@@ -0,0 +1,350 @@+{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE TemplateHaskell      #-}+{-# LANGUAGE TypeFamilies         #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE CPP                  #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Diagrams.Backend.Rasterific.CmdLine+-- Copyright   :  (c) 2014 Diagrams team (see LICENSE)+-- License     :  BSD-style (see LICENSE)+-- Maintainer  :  diagrams-discuss@googlegroups.com+--+-- Convenient creation of command-line-driven executables for+-- rendering diagrams using the Rasterific backend. Create+-- png, tif, bmp, jpg or animated GIF files.+--+-- * 'defaultMain' creates an executable which can render a single+--   diagram at various options.+--+-- * 'multiMain' is like 'defaultMain' but allows for a list of+--   diagrams from which the user can choose one to render.+--+-- * 'animMain' is like 'defaultMain' but for animations instead of+--   diagrams.+--+-- * `gifMain` creates an executable to generate an animated GIF.+--+-- * 'mainWith' is a generic form that does all of the above but with+--   a slightly scarier type.  See "Diagrams.Backend.CmdLine".  This+--   form can also take a function type that has a suitable final result+--   (any of arguments to the above types) and 'Parseable' arguments.+--+-- If you want to generate diagrams programmatically---/i.e./ if you+-- want to do anything more complex than what the below functions+-- provide---you have several options.+--+-- * Use a function with 'mainWith'.  This may require making+--   'Parseable' instances for custom argument types.+--+-- * Make a new 'Mainable' instance.  This may require a newtype+--   wrapper on your diagram type to avoid the existing instances.+--   This gives you more control over argument parsing, intervening+--   steps, and diagram creation.+--+-- * Build option records and pass them along with a diagram to 'mainRender'+--   from "Diagrams.Backend.CmdLine".+--+-- * You can use 'Diagrams.Backend.Rasterific.renderRasterific' to render a+--   diagram to a file directly; see "Diagrams.Backend.Rasterific".+--+-- * A more flexible approach is to directly call 'renderDia'; see+--   "Diagrams.Backend.Rasterific" for more information.+--+-- For a tutorial on command-line diagram creation see+-- <http://projects.haskell.org/diagrams/doc/cmdline.html>.+--+-----------------------------------------------------------------------------+module Diagrams.Backend.Rasterific.CmdLine+        (+         -- * General form of @main@+         -- $mainwith+         mainWith++         -- * Supported forms of @main@+       , defaultMain+       , multiMain+       , animMain+       , gifMain++        -- * GIF support+       , GifOpts(..)++         -- * Backend tokens+       , Rasterific+       , B+       ) where++import           Diagrams.Prelude              hiding (width, height, interval+                                              ,option, (<>))+import           Diagrams.Backend.Rasterific+import           Diagrams.Backend.CmdLine++import           Codec.Picture+import           Codec.Picture.Types          (dropTransparency)+import           Codec.Picture.ColorQuant     (defaultPaletteOptions)++import qualified Data.ByteString.Lazy as L    (ByteString, writeFile)++import           Options.Applicative+import           Control.Lens                 ((^.), Lens', makeLenses)++import           Data.List.Split++#ifdef CMDLINELOOP++import           Data.Maybe                  (fromMaybe)+import           Control.Monad               (when, mplus)+import           Control.Lens                (_1)++import           System.Environment          (getArgs, getProgName)+import           System.Directory            (getModificationTime)+import           System.Process              (runProcess, waitForProcess)+import           System.IO                   (openFile, hClose, IOMode(..)+                                             ,hSetBuffering, BufferMode(..)+                                             ,stdout)+import           System.Exit                 (ExitCode(..))+import           Control.Concurrent          (threadDelay)+import           Control.Exception           (catch, SomeException(..), bracket)+import           System.Posix.Process        (executeFile)++#if MIN_VERSION_directory(1,2,0)++import           Data.Time.Clock             (UTCTime,getCurrentTime)++type ModuleTime = UTCTime+getModuleTime :: IO  ModuleTime+getModuleTime = getCurrentTime++#else++import          System.Time                  (ClockTime, getClockTime)++type ModuleTime = ClockTime+getModuleTime :: IO  ModuleTime+getModuleTime = getClockTime++#endif+#endif++defaultMain :: Diagram Rasterific R2 -> IO ()+defaultMain = mainWith++#ifdef CMDLINELOOP++output' :: Lens' (MainOpts (Diagram Rasterific R2)) FilePath+output' = _1 . output++instance Mainable (Diagram Rasterific R2) where+    type MainOpts (Diagram Rasterific R2) = (DiagramOpts, DiagramLoopOpts)++    mainRender (opts,loopOpts) d = do+        chooseRender opts d+        when (loopOpts^.loop) (waitForChange Nothing loopOpts)+#else++output' :: Lens' (MainOpts (Diagram Rasterific R2)) FilePath+output' = output++instance Mainable (Diagram Rasterific R2) where+    type MainOpts (Diagram Rasterific R2) = DiagramOpts++    mainRender opts d = chooseRender opts d++#endif++chooseRender :: DiagramOpts -> Diagram Rasterific R2 -> IO ()+chooseRender opts d =+  case splitOn "." (opts ^. output) of+    [""] -> putStrLn "No output file given."+    ps | last ps `elem` ["png", "tif", "bmp", "jpg"] -> do+           let img = renderDia Rasterific+                        ( RasterificOptions+                          (mkSizeSpec+                             (fromIntegral <$> opts ^. width )+                             (fromIntegral <$> opts ^. height)+                          )+                        )+                        d+           case last ps of+             "png" -> writePng (opts^.output) img+             "tif" -> writeTiff (opts^.output) img+             "bmp" -> writeBitmap (opts^.output) img+             "jpg" -> writeJpeg 100 (opts^.output) img+             _     -> writePng (opts^.output) img+       | otherwise -> putStrLn $ "Unknown file type: " ++ last ps++-- | @multiMain@ is like 'defaultMain', except instead of a single+--   diagram it takes a list of diagrams paired with names as input.+--   The generated executable then takes a @--selection@ option+--   specifying the name of the diagram that should be rendered.  The+--   list of available diagrams may also be printed by passing the+--   option @--list@.+--+--   Example usage:+--+-- @+-- $ ghc --make MultiTest+-- [1 of 1] Compiling Main             ( MultiTest.hs, MultiTest.o )+-- Linking MultiTest ...+-- $ ./MultiTest --list+-- Available diagrams:+--   foo bar+-- $ ./MultiTest --selection bar -o Bar.png -w 200+-- @++multiMain :: [(String, Diagram Rasterific R2)] -> IO ()+multiMain = mainWith++instance Mainable [(String,Diagram Rasterific R2)] where+    type MainOpts [(String,Diagram Rasterific R2)]+        = (MainOpts (Diagram Rasterific R2), DiagramMultiOpts)++    mainRender = defaultMultiMainRender++-- | @animMain@ is like 'defaultMain', but renders an animation+-- instead of a diagram.  It takes as input an animation and produces+-- a command-line program which will crudely \"render\" the animation+-- by rendering one image for each frame, named by extending the given+-- output file name by consecutive integers.  For example if the given+-- output file name is @foo\/blah.png@, the frames will be saved in+-- @foo\/blah001.png@, @foo\/blah002.png@, and so on (the number of+-- padding digits used depends on the total number of frames).  It is+-- up to the user to take these images and stitch them together into+-- an actual animation format (using, /e.g./ @ffmpeg@).+--+--   Of course, this is a rather crude method of rendering animations;+--   more sophisticated methods will likely be added in the future.+--+-- The @--fpu@ option can be used to control how many frames will be+-- output for each second (unit time) of animation.+animMain :: Animation Rasterific R2 -> IO ()+animMain = mainWith++instance Mainable (Animation Rasterific R2) where+    type MainOpts (Animation Rasterific R2) =+      (MainOpts (Diagram Rasterific R2), DiagramAnimOpts)++    mainRender = defaultAnimMainRender output'++#ifdef CMDLINELOOP++waitForChange :: Maybe ModuleTime -> DiagramLoopOpts -> IO ()+waitForChange lastAttempt opts = do+    prog <- getProgName+    args <- getArgs+    hSetBuffering stdout NoBuffering+    go prog args lastAttempt+  where go prog args lastAtt = do+          threadDelay (1000000 * opts^.interval)+          -- putStrLn $ "Checking... (last attempt = " ++ show lastAttempt ++ ")"+          (newBin, newAttempt) <- recompile lastAtt prog (opts^.src)+          if newBin+            then executeFile prog False args Nothing+            else go prog args $ newAttempt `mplus` lastAtt++-- | @recompile t prog@ attempts to recompile @prog@, assuming the+--   last attempt was made at time @t@.  If @t@ is @Nothing@ assume+--   the last attempt time is the same as the modification time of the+--   binary.  If the source file modification time is later than the+--   last attempt time, then attempt to recompile, and return the time+--   of this attempt.  Otherwise (if nothing has changed since the+--   last attempt), return @Nothing@.  Also return a Bool saying+--   whether a successful recompilation happened.+recompile :: Maybe ModuleTime -> String -> Maybe String -> IO (Bool, Maybe ModuleTime)+recompile lastAttempt prog mSrc = do+  let errFile = prog ++ ".errors"+      srcFile = fromMaybe (prog ++ ".hs") mSrc+  binT <- maybe (getModTime prog) (return . Just) lastAttempt+  srcT <- getModTime srcFile+  if (srcT > binT)+    then do+      putStr "Recompiling..."+      status <- bracket (openFile errFile WriteMode) hClose $ \h ->+        waitForProcess =<< runProcess "ghc" ["--make", srcFile]+                           Nothing Nothing Nothing Nothing (Just h)++      if (status /= ExitSuccess)+        then putStrLn "" >> putStrLn (replicate 75 '-') >> readFile errFile >>= putStr+        else putStrLn "done."++      curTime <- getModuleTime+      return (status == ExitSuccess, Just curTime)++    else return (False, Nothing)++ where getModTime f = catch (Just <$> getModificationTime f)+                            (\(SomeException _) -> return Nothing)+#endif++gifMain :: [(Diagram Rasterific R2, GifDelay)] -> IO ()+gifMain = mainWith++-- | Extra options for animated GIFs.+data GifOpts = GifOpts { _dither :: Bool+                       , _noLooping :: Bool+                       , _loopRepeat :: Maybe Int}++makeLenses ''GifOpts++-- | Command line parser for 'GifOpts'.+--   @--dither@ turn dithering on.+--   @--looping-off@ turn looping off, i.e play GIF once.+--   @--loop-repeat@ number of times to repeat the GIF after the first playing.+--   this option is only used if @--looping-off@ is not set.+instance Parseable GifOpts where+  parser = GifOpts <$> switch+                       ( long "dither"+                      <> help "Turn on dithering." )+                   <*> switch+                       ( long "looping-off"+                      <> help "Turn looping off" )+                   <*> ( optional . option )+                       ( long "loop-repeat"+                      <> help "Number of times to repeat" )++instance Mainable [(Diagram Rasterific R2, GifDelay)] where+    type MainOpts [(Diagram Rasterific R2, GifDelay)] = (DiagramOpts, GifOpts)++    mainRender (dOpts, gOpts) ds = gifRender (dOpts, gOpts) ds++encodeGifAnimation' :: [GifDelay] -> GifLooping -> Bool+                   -> [Image PixelRGB8] -> Either String (L.ByteString)+encodeGifAnimation' delays looping dithering lst =+    encodeGifImages looping triples+      where+        triples = zipWith (\(x,z) y -> (x, y, z)) doubles delays+        doubles = [(pal, img)+                | (img, pal) <- palettize+                   defaultPaletteOptions {enableImageDithering=dithering} <$> lst]++writeGifAnimation' :: FilePath -> [GifDelay] -> GifLooping -> Bool+                  -> [Image PixelRGB8] -> Either String (IO ())+writeGifAnimation' path delays looping dithering img =+    L.writeFile path <$> encodeGifAnimation' delays looping dithering img++gifRender :: (DiagramOpts, GifOpts) -> [(Diagram Rasterific R2, GifDelay)] -> IO ()+gifRender (dOpts, gOpts) lst =+  case splitOn "." (dOpts^.output) of+    [""] -> putStrLn "No output file given"+    ps | last ps == "gif" -> do+           let looping = if gOpts^.noLooping+                         then LoopingNever+                         else case gOpts^.loopRepeat of+                                Nothing -> LoopingForever+                                Just n  -> LoopingRepeat (fromIntegral n)+               dias = map fst lst+               delays = map snd lst+               sizeSpec = mkSizeSpec (fromIntegral <$> dOpts^.width)+                                 (fromIntegral <$> dOpts^.height)+               opts = RasterificOptions sizeSpec+               imageRGB8s = map (pixelMap dropTransparency+                               . renderDia Rasterific opts) dias+               result = writeGifAnimation' (dOpts^.output) delays+                                            looping (gOpts^.dither)+                                            imageRGB8s+           case result of+             Left s   -> putStrLn s+             Right io -> io+       | otherwise -> putStrLn $ "File name must end with .gif"