packages feed

pixelated-avatar-generator (empty) → 0.1.0

raw patch · 11 files changed

+594/−0 lines, 11 filesdep +JuicyPixelsdep +QuickCheckdep +basesetup-changedbinary-added

Dependencies added: JuicyPixels, QuickCheck, base, bytestring, hspec, pixelated-avatar-generator, pureMD5, split

Files

+ CHANGELOG.md view
@@ -0,0 +1,6 @@+# Changelog++## 0.1.0+* Implement generation and saving of avatars.+* Implement avatar upscaling.+* Create example executable program that makes use of the library.
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2016 Christopher Wells++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,50 @@+# Pixelated Avatar Generator [![Travis CI Status](https://api.travis-ci.org/ExcaliburZero/pixelated-avatar-generator.svg)](https://travis-ci.org/ExcaliburZero/pixelated-avatar-generator) [![Coverage Status](https://coveralls.io/repos/github/ExcaliburZero/pixelated-avatar-generator/badge.svg?branch=master)](https://coveralls.io/github/ExcaliburZero/pixelated-avatar-generator?branch=master)+Pixelated Avatar Generator is a Haskell library and application for generating pixelated avatar images from seed values.++```haskell+import Graphics.Avatars.Pixelated++createAndSaveAvatar :: String -> FilePath -> IO ()+createAndSaveAvatar s path = saveAvatar avatar path+  where avatar = scaleAvatar 32 $ generateAvatar seed+        seed   = createSeed s+```++![An example of an avatar generated by the library.](example_image.png)++## Executable+An example executable program that uses the library is also provided. It creates an avatar from a given seed string and saves the created `.png` image to a given file location.++The executable can be compiled by running the following command:++```+$ stack build+```++The executable can then by run by running it with `stack exec` and providing it the desired filepath of the output file including the `.png` extension and a random seed string.++```+$ stack exec pixelated-avatar-generator image.png "Hello, World"+Creating avatar at image.png+Grey+█ ████ █+        +  ████  +█  ██  █+████████+█ █  █ █+█      █+████████+Successfully created avatar, and saved it to image.png+```++### Usage+```+Usage: pixelated-avatar-generator FILEPATH SEEDSTRING++FILEPATH   -- The location to save the generated avatar at. "img/test.png"+SEEDSTRING -- The string to use to generate the avatar. "Hello"+```++## License+The source code of Pixelated Avatar Generator is available under the [MIT license](https://opensource.org/licenses/MIT), see `LICENSE` for more information.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,35 @@+module Main where++import Graphics.Avatars.Pixelated+import System.Environment (getArgs)++main :: IO ()+main = do+  args <- getArgs+  checkForProperArgs args+  let path = head args+  let seedString = args !! 1+  putStrLn $ "Creating avatar at " ++ path+  createAndSaveAvatar path seedString+  putStrLn $ "Successfully created avatar, and saved it to " ++ path++checkForProperArgs :: [String] -> IO ()+checkForProperArgs args = if length args /= 2+                          then error $ "Improper number of arguments.\n\n" ++ usageInfo+                          else return ()++usageInfo :: String+usageInfo = unlines [+     "Usage: pixelated-avatar-generator FILEPATH SEEDSTRING"+   , ""+   , "FILEPATH   -- The location to save the generated avatar at. \"img/test.png\""+   , "SEEDSTRING -- The string to use to generate the avatar. \"Hello\""+  ]++createAndSaveAvatar :: FilePath -> String -> IO ()+createAndSaveAvatar path s = do+  let seed = createSeed s+  let avatar = generateAvatar seed+  print avatar+  let avatarScaled = scaleAvatar 32 avatar+  saveAvatar avatarScaled path
+ example_image.png view

binary file changed (absent → 1711 bytes)

+ pixelated-avatar-generator.cabal view
@@ -0,0 +1,62 @@+name:                pixelated-avatar-generator+version:             0.1.0+synopsis:            A library and application for generating pixelated avatars.+description:+                     <<data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEWWlpb///+oiwZLAAAAUElEQVRo3u3XsQ0AIAgAQfZfGhsLoxAH4L7CeLXBiF1mZjUHAADAFQAAQHX5IAAARoLzUM4AAIwF9igAAH5AkqruR6NdLgAAGAX8swAAaMACTyHw4mh6Rv4AAAAASUVORK5CYII=>>++                     .++                     Pixelated Avatar Generator is a library and application+                     for generating pixelated avatar images from seed strings.+homepage:            https://github.com/ExcaliburZero/pixelated-avatar-generator+bug-reports:         https://github.com/ExcaliburZero/pixelated-avatar-generator/issues+license:             MIT+license-file:        LICENSE+author:              Christopher Wells+maintainer:          cwellsny@nycap.rr.com+copyright:           2016 Christopher Wells+stability:           Experimental+category:            Image, Graphics+build-type:          Simple+extra-source-files:  README.md+                     CHANGELOG.md+                     example_image.png+                     test/Graphics/Avatars/helloAvatar.png+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Graphics.Avatars.Pixelated+  build-depends:       base >= 4.7 && < 5+                     , JuicyPixels >= 3.2.7+                     , bytestring >= 0.10.6.0+                     , pureMD5 >= 2.1.0.0+                     , split >= 0.2.3.0+  ghc-options:         -Wall+  default-language:    Haskell2010++executable pixelated-avatar-generator+  hs-source-dirs:      app+  main-is:             Main.hs+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall+  build-depends:       base+                     , pixelated-avatar-generator+  default-language:    Haskell2010++test-suite unit-tests+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  other-modules:       Graphics.Avatars.PixelatedSpec+  build-depends:       base+                     , JuicyPixels >= 3.2.7+                     , bytestring >= 0.10.6.0+                     , pixelated-avatar-generator+                     , hspec+                     , QuickCheck+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/ExcaliburZero/pixelated-avatar-generator
+ src/Graphics/Avatars/Pixelated.hs view
@@ -0,0 +1,238 @@+-- |+-- Module      : Graphics.Avatars.Pixelated+-- Description : Contains types and functions for generating pixelated avatars.+-- Copyright   : (c) Christopher Wells, 2016+-- License     : MIT+-- Maintainer  : cwellsny@nycap.rr.com+--+-- This module provides types and functions for creating and working with+-- pixelated avatars.+--+-- Avatars can be generated by providing a `Seed`. Seeds can be created by+-- passing a random String into `createSeed`. The given String is then turned+-- into an MD5 checksum, which is used as the seed value.+--+-- Once a Seed has been created, an Avatar can be generated from it by passing+-- it into `generateAvatar`. By default, Avatars start at a size of 8x8px. The+-- size of an Avatar can be increased by passing it into `scaleAvatar`. Once+-- you have scaled the Avatar to the size you want it to be it can then be+-- saved to a file by passing it into `saveAvatar`.+--+-- = Example+-- The following is an example showing how to construct a function which will+-- generate a 256x256px avatar from a given seed string, and save it at the+-- given location.+--+-- @+-- import Graphics.Avatars.Pixelated+--+-- createAndSaveAvatar :: String -> FilePath -> IO ()+-- createAndSaveAvatar s path = saveAvatar avatar path+--   where avatar = scaleAvatar 32 $ generateAvatar seed+--         seed   = createSeed s+-- @+module Graphics.Avatars.Pixelated+(+  -- * Types+  -- ** Seed+  Seed(..), createSeed,++  -- ** Avatar+  Avatar(..), generateAvatar, scaleAvatar, saveAvatar, convertAvatarToImage,++  -- ** Color+  Color(..), getColorValue, colorFromSeed,+  +  -- ** Avatar Grid+  AvatarGrid(..), showGrid, generateAvatarGrid,++  -- ** Utility+  scaleList+)+where++import Codec.Picture (encodePng, generateImage, Image(..), PixelRGB8(..))+import Data.Char (ord)+import qualified Data.ByteString.Lazy as B (writeFile)+import Data.ByteString.Lazy.Internal (packChars)+import Data.Digest.Pure.MD5 (md5)+import Data.List.Split (chunksOf)++-------------------------------------------------------------------------------+-- Seeds++-- | A seed to use in generating an avatar. Can be created from a String by+-- using the `createSeed` function.+--+-- Seeds are expected to be 32 character hexidecimal MD5 checksums.+newtype Seed = Seed { unSeed :: String }+  deriving (Eq, Show)++-- | Creates a seed from a given String.+--+-- >>> createSeed "Hello"+-- Seed {unSeed = "8b1a9953c4611296a827abf8c47804d7"}+createSeed :: String -> Seed+createSeed = Seed . show . md5 . packChars++-------------------------------------------------------------------------------+-- Avatars++-- | A generated avatar.+data Avatar = Avatar {+      color :: Color+    , grid  :: AvatarGrid+  }+  deriving (Eq)++instance Show Avatar where+  show a = (show . color) a ++ "\n" ++ ((show . grid) a)++-- | Generates an avatar from the given seed.+generateAvatar :: Seed -> Avatar+generateAvatar seed = avatar+ where avatar = Avatar {+             color = aColor+           , grid = aGrid+         }+       aColor = colorFromSeed seed+       aGrid = generateAvatarGrid seed++-- | Scales the given Avatar by the given scaling factor.+scaleAvatar :: Int -> Avatar -> Avatar+scaleAvatar factor avatar = avatar { grid = AvatarGrid scaledGrid }+  where scaledGrid = ((scaleList factor) . (map (scaleList factor))) unscaledGrid+        unscaledGrid = unAvatarGrid $ grid avatar++-- | Saves the given avatar to the given file path.+--+-- @+-- makeAvatar :: Seed -> FilePath -> IO ()+-- makeAvatar seed path = do+--   let avatar = generateAvatar seed path+--   saveAvatar avatar path+-- @+saveAvatar :: Avatar -> FilePath -> IO ()+saveAvatar avatar path = B.writeFile path image+ where image = encodePng $ convertAvatarToImage avatar++-- | Converts the given Avatar into an Image.+convertAvatarToImage :: Avatar -> Image PixelRGB8+convertAvatarToImage avatar = image+  where image = generateImage getPixel dimension dimension+        dimension = length colorGrid+        getPixel x y = colorGrid !! y !! x+        colorGrid = (map . map) (toPixel $ color avatar) $ unAvatarGrid $ grid avatar+        toPixel c v = if v then getColorValue c else PixelRGB8 255 255 255++-------------------------------------------------------------------------------+-- Colors++-- | A color for an avatar.+data Color = Black | Blue | Green | Grey | Orange | Purple | Red | Yellow+  deriving (Eq, Show, Enum)++-- | Converts the given color into a RGB pixel representation.+getColorValue :: Color -> PixelRGB8+getColorValue c+  | c == Black  = PixelRGB8 0   0   0+  | c == Blue   = PixelRGB8 0   0   200+  | c == Green  = PixelRGB8 0   200 0+  | c == Grey   = PixelRGB8 150 150 150+  | c == Orange = PixelRGB8 255 140 65+  | c == Purple = PixelRGB8 130 0   130+  | c == Red    = PixelRGB8 200 0   0+  | otherwise   = PixelRGB8 230 230 0++-- | Picks an avatar color using the given seed.+--+-- >>> colorFromSeed $ Seed {unSeed = "8b1a9953c4611296a827abf8c47804d7"}+-- Grey+colorFromSeed :: Seed -> Color+colorFromSeed = genColor . dSum . unSeed+  where twoDigits n = map ord $ take 2 n+        dSum n = foldr (+) 1 $ twoDigits n+        genColor a = [Black .. Yellow] !! (a `mod` 8)++-------------------------------------------------------------------------------+-- AvatarGrids++-- | A grid of boolean values representing an Avatar. True values indicate+-- colored pixels, and False values indicate blank pixels.+newtype AvatarGrid = AvatarGrid { unAvatarGrid :: [[Bool]] }+  deriving (Eq)++-- | Converts the grid into a String representation.+instance Show AvatarGrid where+  show x = (showGrid . unAvatarGrid) x++-- | The left half of an AvatarGrid.+newtype AvatarGridSide = AvatarGridSide { unAvatarGridSide :: [[Bool]] }++-- | Converts the grid side into a String representation.+instance Show AvatarGridSide where+  show x = (showGrid . unAvatarGridSide) x++-- | Converts a grid of boolean values into a String representation.+--+-- >>> putStrLn $ showGrid [[True, False], [False, True]]+-- █ +--  █+showGrid :: [[Bool]] -> String+showGrid g = (init . unlines) $ (map . map) showPixel g++-- | Converts a boolean value into a character representation for a pixel.+--+-- >>> showPixel True+-- '\9608'+-- >>> showPixel False+-- ' '+showPixel :: Bool -> Char+showPixel p = if p then '█' else ' '++-- | Generates an AvatarGrid using the given Seed.+--+-- It works by generating the left half of the grid using the contents of the+-- Seed, and then mirroring the left half to create the full grid.+--+-- >>> generateAvatarGrid Seed {unSeed = "8b1a9953c4611296a827abf8c47804d7"}+-- ██ ██ ██+-- ██    ██+-- █      █+--   █  █  +-- ██    ██+-- ████████+-- █  ██  █+--   █  █  +generateAvatarGrid :: Seed -> AvatarGrid+generateAvatarGrid = mirrorGrid . generateAvatarGridSide++-- | Creates a full AvatarGrid by mirroring the given AvatarGridSide on the+-- y-axis.+mirrorGrid :: AvatarGridSide -> AvatarGrid+mirrorGrid side = AvatarGrid $ map mirror $ unAvatarGridSide side+  where mirror l = l ++ (reverse l)++-- | Generates the right side of an AvatarGrid using the given seed.+generateAvatarGridSide :: Seed -> AvatarGridSide+generateAvatarGridSide = AvatarGridSide . numToGrid . unSeed++-- | Converts the given hexidecimal number String into a grid of boolean values.+numToGrid :: String -> [[Bool]]+numToGrid s = boolGrid+  where boolGrid = (map . map) convertToPixel $ (map . map) ord numGrid+        numGrid  = chunksOf 4 s+        convertToPixel = (> ord '7')++-------------------------------------------------------------------------------+-- Utilities++-- | Scales the given list by the given scaling factor.+--+-- >>> scaleList 2 [1, 2]+-- [1,1,2,2]+-- >>> scaleList 3 [0, 1]+-- [0,0,0,1,1,1]+scaleList :: Int -> [a] -> [a]+scaleList _     []     = []+scaleList factor (x:xs) = replicate factor x ++ scaleList factor xs
+ test/Graphics/Avatars/PixelatedSpec.hs view
@@ -0,0 +1,179 @@+module Graphics.Avatars.PixelatedSpec (main, spec) where++import Test.Hspec+import Test.QuickCheck++import Codec.Picture+import qualified Data.ByteString.Lazy as B (ByteString(..))+import System.IO.Unsafe (unsafePerformIO)++import Graphics.Avatars.Pixelated++-- `main` is here so that this module can be run from GHCi on its own.  It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec++spec :: Spec+spec = do++  -----------------------------------------------------------------------------+  -- Seeds++  describe "createSeed" $ do+    it "creates a seed using an md5 checksum" $ do+      createSeed "" `shouldBe` Seed {unSeed = "d41d8cd98f00b204e9800998ecf8427e"}++  -----------------------------------------------------------------------------+  -- Avatars++  describe "Avatar" $ do+    it "can be compared for equality" $ do+      helloAvatar == helloAvatar `shouldBe` True+      helloAvatar /= testAvatar  `shouldBe` True+    it "can be converted into a string representation" $ do+      show helloAvatar `shouldBe` "Grey\n" ++ helloAvatarGridString++  describe "generateAvatar" $ do+    it "generates an avatar from a seed" $ do+      generateAvatar helloSeed `shouldBe` helloAvatar++  describe "scaleAvatar" $ do+    it "scales an avatar by a given factor" $ do+      scaleAvatar 2 testAvatar `shouldBe` testAvatar2x++  describe "convertAvatarToImage" $ do+    it "converts an avatar into an image" $ do+      encodePng (convertAvatarToImage helloAvatar) `shouldBe` helloAvatarImage++  -----------------------------------------------------------------------------+  -- Colors++  describe "colorFromSeed" $ do+    it "picks a color based on the given seed" $ do+      colorFromSeed (helloSeed) `shouldBe` Grey+    it "can choose Black" $ do+      colorFromSeed (Seed {unSeed = "c4000000000000000000000000000000"}) `shouldBe` Black+    it "can choose Blue" $ do+      colorFromSeed (Seed {unSeed = "ec000000000000000000000000000000"}) `shouldBe` Blue+    it "can choose Green" $ do+      colorFromSeed (Seed {unSeed = "a8000000000000000000000000000000"}) `shouldBe` Green+    it "can choose Grey" $ do+      colorFromSeed (Seed {unSeed = "aa000000000000000000000000000000"}) `shouldBe` Grey+    it "can choose Orange" $ do+      colorFromSeed (Seed {unSeed = "c8000000000000000000000000000000"}) `shouldBe` Orange+    it "can choose Purple" $ do+      colorFromSeed (Seed {unSeed = "c9000000000000000000000000000000"}) `shouldBe` Purple+    it "can choose Red" $ do+      colorFromSeed (Seed {unSeed = "c2000000000000000000000000000000"}) `shouldBe` Red+    it "can choose Yellow" $ do+      colorFromSeed (Seed {unSeed = "8f000000000000000000000000000000"}) `shouldBe` Yellow++  describe "getColorValue" $ do+    it "returns a pixel representation of a color" $ do+      getColorValue Orange `shouldBe` PixelRGB8 255 140 65+    it "can process Black" $ do+      getColorValue Black `shouldBe` PixelRGB8 0 0 0+    it "can process Blue" $ do+      getColorValue Blue `shouldBe` PixelRGB8 0 0 200+    it "can process Green" $ do+      getColorValue Green `shouldBe` PixelRGB8 0 200 0+    it "can process Grey" $ do+      getColorValue Grey `shouldBe` PixelRGB8 150 150 150+    it "can process Orange" $ do+      getColorValue Orange `shouldBe` PixelRGB8 255 140 65+    it "can process Purple" $ do+      getColorValue Purple `shouldBe` PixelRGB8 130 0 130+    it "can process Red" $ do+      getColorValue Red `shouldBe` PixelRGB8 200 0 0+    it "can process Yellow" $ do+      getColorValue Yellow `shouldBe` PixelRGB8 230 230 0++  -----------------------------------------------------------------------------+  -- Avatar Grids++  describe "generateAvatarGrid" $ do+    it "creates an avatar grid from a seed" $ do+      generateAvatarGrid helloSeed `shouldBe` helloAvatarGrid++  describe "AvatarGrid" $ do+    it "can be represented as a String" $ do+      show helloAvatarGrid `shouldBe` helloAvatarGridString++  -----------------------------------------------------------------------------+  -- Utilities++  describe "scaleList" $ do+   it "scales a list by a given factor" $ do+     scaleList 3 [0, 1] `shouldBe` [0, 0, 0, 1, 1, 1]+   it "can scale a list by a factor of one" $ do+     scaleList 1 [0, 1] `shouldBe` [0, 1]++-------------------------------------------------------------------------------+-- Values++helloSeed :: Seed+helloSeed = Seed {unSeed = "8b1a9953c4611296a827abf8c47804d7"}++helloAvatar :: Avatar+helloAvatar = Avatar {+      color = Grey+    , grid  = helloAvatarGrid+  }++helloAvatarGrid :: AvatarGrid+helloAvatarGrid = AvatarGrid ([+      [True, True, False, True, True, False, True, True]+    , [True, True, False, False, False, False, True, True]+    , [True, False, False, False, False, False, False, True]+    , [False, False, True, False, False, True, False, False]+    , [True, True, False, False, False, False, True, True]+    , [True, True, True, True, True, True, True, True]+    , [True, False, False, True, True, False, False, True]+    , [False, False, True, False, False, True, False, False]+  ])++helloAvatarGridString :: String+helloAvatarGridString = (init . unlines) [+    "██ ██ ██"+  , "██    ██"+  , "█      █"+  , "  █  █  "+  , "██    ██"+  , "████████"+  , "█  ██  █"+  , "  █  █  "+  ]++helloAvatarImage :: B.ByteString+helloAvatarImage = image+  where image = unRight $ encodeDynamicPng bytestring+        bytestring = unRight $ unsafePerformIO $ readImage "test/Graphics/Avatars/helloAvatar.png"++testAvatar :: Avatar+testAvatar = Avatar {+      color = Orange+    , grid  = AvatarGrid [+         [True, False]+       , [False, True]+      ]+  }++testAvatar2x :: Avatar+testAvatar2x = Avatar {+      color = Orange+    , grid  = AvatarGrid [+         [True, True, False, False]+       , [True, True, False, False]+       , [False, False, True, True]+       , [False, False, True, True]+      ]+  }++-------------------------------------------------------------------------------+-- Utility Functions++unRight :: Either a b -> b+unRight x = case x of+  Right y -> y+  Left _  -> error "Unexpected Left"
+ test/Graphics/Avatars/helloAvatar.png view

binary file changed (absent → 100 bytes)

+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}