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 +6/−0
- LICENSE +21/−0
- README.md +50/−0
- Setup.hs +2/−0
- app/Main.hs +35/−0
- example_image.png binary
- pixelated-avatar-generator.cabal +62/−0
- src/Graphics/Avatars/Pixelated.hs +238/−0
- test/Graphics/Avatars/PixelatedSpec.hs +179/−0
- test/Graphics/Avatars/helloAvatar.png binary
- test/Spec.hs +1/−0
+ 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 [](https://travis-ci.org/ExcaliburZero/pixelated-avatar-generator) [](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+```++++## 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 #-}