diff options
author | Griffin Smith <root@gws.fyi> | 2019-12-25T00·40-0500 |
---|---|---|
committer | Griffin Smith <root@gws.fyi> | 2019-12-30T16·24-0500 |
commit | 6f427fe4d6ba9a03f122d15839298040a7cfb925 (patch) | |
tree | 5ab6c11d7b1f4109e1d196f9d1135cbb4bd828f6 | |
parent | 13516911366a484ee5484166520133e056010515 (diff) |
Fix circle rendering, add filled circle
Make raster circle rendering use the Rasterific package instead of attempting desperately to hand-roll it, and add a method for generating filled circles.
-rw-r--r-- | package.yaml | 3 | ||||
-rw-r--r-- | src/Xanthous/Generators/Util.hs | 49 | ||||
-rw-r--r-- | src/Xanthous/Util.hs | 10 | ||||
-rw-r--r-- | src/Xanthous/Util/Graphics.hs | 81 | ||||
-rw-r--r-- | test/Xanthous/Util/GraphicsSpec.hs | 33 |
5 files changed, 124 insertions, 52 deletions
diff --git a/package.yaml b/package.yaml index b4c53308078e..72eb0d32a669 100644 --- a/package.yaml +++ b/package.yaml @@ -36,6 +36,7 @@ dependencies: - generic-monoid - generic-lens - groups +- JuicyPixels - lens - megaparsec - MonadRandom @@ -47,7 +48,9 @@ dependencies: - random-source - raw-strings-qq - reflection +- Rasterific - stache +- semigroupoids - tomland - text-zipper - vector diff --git a/src/Xanthous/Generators/Util.hs b/src/Xanthous/Generators/Util.hs index 8fd04c0b9326..2c041149d900 100644 --- a/src/Xanthous/Generators/Util.hs +++ b/src/Xanthous/Generators/Util.hs @@ -1,4 +1,6 @@ {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE AllowAmbiguousTypes #-} -------------------------------------------------------------------------------- module Xanthous.Generators.Util ( MCells @@ -13,18 +15,22 @@ module Xanthous.Generators.Util , regions , fillAll , fillAllM + , fromPoints + , fromPointsM ) where -------------------------------------------------------------------------------- -import Xanthous.Prelude hiding (Foldable, toList, for_) -import Data.Array.ST -import Data.Array.Unboxed -import Control.Monad.ST -import Control.Monad.Random -import Data.Monoid -import Data.Foldable (Foldable, toList, for_) +import Xanthous.Prelude hiding (Foldable, toList, for_) +import Data.Array.ST +import Data.Array.Unboxed +import Control.Monad.ST +import Control.Monad.Random +import Data.Monoid +import Data.Foldable (Foldable, toList, for_) +import qualified Data.Set as Set +import Data.Semigroup.Foldable -------------------------------------------------------------------------------- -import Xanthous.Util (foldlMapM') -import Xanthous.Data (Dimensions, width, height) +import Xanthous.Util (foldlMapM', maximum1, minimum1) +import Xanthous.Data (Dimensions, width, height) -------------------------------------------------------------------------------- type MCells s = STUArray s (Word, Word) Bool @@ -184,3 +190,28 @@ fillAll ixes a = accum (const fst) a $ (, (False, ())) <$> toList ixes fillAllM :: (MArray a Bool m, Ix i, Foldable f) => f i -> a i Bool -> m () fillAllM ixes a = for_ ixes $ \i -> writeArray a i False + +fromPoints + :: forall a f i. + ( IArray a Bool + , Ix i + , Functor f + , Foldable1 f + ) + => f (i, i) + -> a (i, i) Bool +fromPoints points = + let pts = Set.fromList $ toList points + dims = ( (minimum1 $ fst <$> points, minimum1 $ snd <$> points) + , (maximum1 $ fst <$> points, maximum1 $ snd <$> points) + ) + in array dims $ range dims <&> \i -> (i, i `member` pts) + +fromPointsM + :: (MArray a Bool m, Ix i, Element f ~ i, MonoFoldable f) + => NonNull f + -> m (a i Bool) +fromPointsM points = do + arr <- newArray (minimum points, maximum points) False + fillAllM (otoList points) arr + pure arr diff --git a/src/Xanthous/Util.hs b/src/Xanthous/Util.hs index b8b789e1b1ea..93155af3fd59 100644 --- a/src/Xanthous/Util.hs +++ b/src/Xanthous/Util.hs @@ -26,6 +26,8 @@ module Xanthous.Util , takeWhileInclusive , smallestNotIn , removeVectorIndex + , maximum1 + , minimum1 -- * Type-level programming utils , KnownBool(..) @@ -38,6 +40,8 @@ import Data.Foldable (foldr) import Data.Monoid import Data.Proxy import qualified Data.Vector as V +import Data.Semigroup (Max(..), Min(..)) +import Data.Semigroup.Foldable -------------------------------------------------------------------------------- newtype EqEqProp a = EqEqProp a @@ -218,6 +222,12 @@ removeVectorIndex idx vect = let (before, after) = V.splitAt idx vect in before <> fromMaybe Empty (tailMay after) +maximum1 :: (Ord a, Foldable1 f) => f a -> a +maximum1 = getMax . foldMap1 Max + +minimum1 :: (Ord a, Foldable1 f) => f a -> a +minimum1 = getMin . foldMap1 Min + -------------------------------------------------------------------------------- -- | This class gives a boolean associated with a type-level bool, a'la diff --git a/src/Xanthous/Util/Graphics.hs b/src/Xanthous/Util/Graphics.hs index 3dc2f6f14cac..e8269e72d6c8 100644 --- a/src/Xanthous/Util/Graphics.hs +++ b/src/Xanthous/Util/Graphics.hs @@ -1,44 +1,65 @@ -- | Graphics algorithms and utils for rendering things in 2D space -------------------------------------------------------------------------------- -module Xanthous.Util.Graphics where +module Xanthous.Util.Graphics + ( circle + , filledCircle + , line + ) where -------------------------------------------------------------------------------- -import Xanthous.Prelude -import Data.List (unfoldr) +import Xanthous.Prelude +import Data.List (unfoldr) +import Data.Ix (range, Ix) +import Data.Word (Word8) +import qualified Graphics.Rasterific as Raster +import Graphics.Rasterific hiding (circle, line) +import Graphics.Rasterific.Texture (uniformTexture) +import Codec.Picture (imagePixels) -------------------------------------------------------------------------------- --- | Generate a circle centered at the given point and with the given radius --- using the <midpoint circle algorithm --- https://en.wikipedia.org/wiki/Midpoint_circle_algorithm>. --- --- Code taken from <https://rosettacode.org/wiki/Bitmap/Midpoint_circle_algorithm#Haskell> -circle :: (Num i, Ord i) + +circle :: (Num i, Integral i, Ix i) => (i, i) -- ^ center -> i -- ^ radius -> [(i, i)] -circle (x₀, y₀) radius - -- Four initial points, plus the generated points - = (x₀, y₀ + radius) : (x₀, y₀ - radius) : (x₀ + radius, y₀) : (x₀ - radius, y₀) : points - where - -- Creates the (x, y) octet offsets, then maps them to absolute points in all octets. - points = concatMap generatePoints $ unfoldr step initialValues +circle (ox, oy) radius + = pointsFromRaster (ox + radius) (oy + radius) + $ stroke 1 JoinRound (CapRound, CapRound) + $ Raster.circle (V2 (fromIntegral ox) (fromIntegral oy)) + $ fromIntegral radius - generatePoints (x, y) - = [ (x₀ `xop` x', y₀ `yop` y') - | (x', y') <- [(x, y), (y, x)] - , xop <- [(+), (-)] - , yop <- [(+), (-)] - ] +filledCircle :: (Num i, Integral i, Ix i) + => (i, i) -- ^ center + -> i -- ^ radius + -> [(i, i)] +filledCircle (ox, oy) radius + = pointsFromRaster (ox + radius) (oy + radius) + $ fill + $ Raster.circle (V2 (fromIntegral ox) (fromIntegral oy)) + $ fromIntegral radius + +-- showCells . fromPoints . NE.fromList $ filledCircle (15, 15) 7 +-- pointsFromRaster :: (Num i, Integral i, Ix i) +-- => i -- ^ width +-- -> i -- ^ height +-- -> _ +-- -> [(i, i)] +pointsFromRaster + :: (Integral a, Integral b, Ix a, Ix b) + => a + -> b + -> Drawing Word8 () + -> [(a, b)] +pointsFromRaster w h raster + = map snd + $ filter ((== 1) . fst) + $ zip pixels + $ range ((1, 1), (w, h)) + where + pixels = toListOf imagePixels + $ renderDrawing @Word8 (fromIntegral w) (fromIntegral h) 0 + $ withTexture (uniformTexture 1) raster - initialValues = (1 - radius, 1, (-2) * radius, 0, radius) - step (f, ddf_x, ddf_y, x, y) - | x >= y = Nothing - | otherwise = Just ((x', y'), (f', ddf_x', ddf_y', x', y')) - where - (f', ddf_y', y') | f >= 0 = (f + ddf_y' + ddf_x', ddf_y + 2, y - 1) - | otherwise = (f + ddf_x, ddf_y, y) - ddf_x' = ddf_x + 2 - x' = x + 1 -- | Draw a line between two points using Bresenham's line drawing algorithm -- diff --git a/test/Xanthous/Util/GraphicsSpec.hs b/test/Xanthous/Util/GraphicsSpec.hs index 4b761dc51fe9..ecd6dbe19197 100644 --- a/test/Xanthous/Util/GraphicsSpec.hs +++ b/test/Xanthous/Util/GraphicsSpec.hs @@ -15,19 +15,26 @@ test = testGroup "Xanthous.Util.Graphics" [ testGroup "circle" [ testCase "radius 12, origin 0" $ (sort . unique @[] @[_]) (circle @Int (0, 0) 12) - @?= (sort . unique) ( - let quadrant = - [ (0, 12) , (1, 12) , (2, 12) , (3, 12) - , (4, 12) , (5, 11) , (6, 11) , (7, 10) - , (8, 9) , (9, 9) , (9, 8) , (10, 7) - , (11, 6) , (11, 5) , (12, 4) , (12, 3) - , (12, 2) , (12, 1) , (12, 0) - ] - in quadrant - <> (quadrant <&> _1 %~ negate) - <> (quadrant <&> _2 %~ negate) - <> (quadrant <&> both %~ negate) - ) + @?= [ (1,12) + , (2,12) + , (3,12) + , (4,12) + , (5,12) + , (6,11) + , (7,10) + , (7,11) + , (8,10) + , (9,9) + , (10,7) + , (10,8) + , (11,6) + , (11,7) + , (12,1) + , (12,2) + , (12,3) + , (12,4) + , (12,5) + ] ] , testGroup "line" |