diff --git a/README.md b/README.md index 1f779002..5fe3d382 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,12 @@ a NixOS module; how to do this is shown in the example above. { pkgs, ... }: { - # A colorscheme will be chosen automatically based on your wallpaper + # A colorscheme will be chosen automatically based on your wallpaper. stylix.image = ./wallpaper.png; + # Use this option to force a light or dark theme. + stylix.polarity = "light"; + # Select your preferred fonts, or use these defaults: stylix.fonts = { serif = { diff --git a/palette-generator/Stylix/Main.hs b/palette-generator/Stylix/Main.hs index 1448b6dc..7b5ffee7 100644 --- a/palette-generator/Stylix/Main.hs +++ b/palette-generator/Stylix/Main.hs @@ -11,10 +11,11 @@ import Text.JSON ( encode ) -- | Run the genetic algorithm to generate a palette from the given image. selectColours :: (Floating a, Real a) - => V.Vector (LAB a) -- ^ Colours of the source image + => String -- ^ Scheme type: "either", "light" or "dark" + -> V.Vector (LAB a) -- ^ Colours of the source image -> V.Vector (LAB a) -- ^ Generated palette -selectColours image - = snd $ evolve image (EvolutionConfig 1000 100 0.5 150) (mkStdGen 0) +selectColours polarity image + = snd $ evolve (polarity, image) (EvolutionConfig 1000 100 0.5 150) (mkStdGen 0) -- | Convert a 'DynamicImage' to a simple 'V.Vector' of colours. unpackImage :: (Num a) => DynamicImage -> V.Vector (RGB a) @@ -30,22 +31,23 @@ loadImage :: String -- ^ Path to the file -> IO DynamicImage loadImage input = either error id <$> readImage input -mainProcess :: (String, String) -> IO () -mainProcess (input, output) = do +mainProcess :: (String, String, String) -> IO () +mainProcess (polarity, input, output) = do putStrLn $ "Processing " ++ input image <- loadImage input let outputTable = makeOutputTable $ V.map lab2rgb - $ selectColours + $ selectColours polarity $ V.map rgb2lab $ unpackImage image writeFile output $ encode outputTable putStrLn $ "Saved to " ++ output -parseArguments :: [String] -> Either String (String, String) -parseArguments [input, output] = Right (input, output) -parseArguments [_] = Left "Please specify an output file" -parseArguments [] = Left "Please specify an image" +parseArguments :: [String] -> Either String (String, String, String) +parseArguments [polarity, input, output] = Right (polarity, input, output) +parseArguments [_, _] = Left "Please specify an output file" +parseArguments [_] = Left "Please specify an image" +parseArguments [] = Left "Please specify a polarity: either, light or dark" parseArguments _ = Left "Too many arguments" main :: IO () diff --git a/palette-generator/Stylix/Palette.hs b/palette-generator/Stylix/Palette.hs index 64da6a1e..0b1d5974 100644 --- a/palette-generator/Stylix/Palette.hs +++ b/palette-generator/Stylix/Palette.hs @@ -34,12 +34,12 @@ randomFromVector generator vector = let (index, generator') = randomR (0, V.length vector - 1) generator in (vector ! index, generator') -instance (Floating a, Real a) => Species (V.Vector (LAB a)) (V.Vector (LAB a)) where +instance (Floating a, Real a) => Species (String, (V.Vector (LAB a))) (V.Vector (LAB a)) where {- | Palettes in the initial population are created by randomly sampling 16 colours from the source image. -} - generate image = generateColour 16 + generate (_, image) = generateColour 16 where generateColour 0 generator = (generator, V.empty) generateColour n generator = let (colour, generator') = randomFromVector generator image @@ -51,17 +51,13 @@ instance (Floating a, Real a) => Species (V.Vector (LAB a)) (V.Vector (LAB a)) w Mutation is done by replacing a random slot in the palette with a new colour, which is randomly sampled from the source image. -} - mutate image generator palette + mutate (_, image) generator palette = let (index, generator') = randomR (0, 15) generator (colour, generator'') = randomFromVector generator' image in (generator'', palette // [(index, colour)]) - fitness _ palette - = realToFrac $ - accentDifference - - -- Either light schemes or dark themes are allowed. - -- We try to converge on whichever theme we are closer to. - min lightScheme darkScheme + fitness (polarity, _) palette + = realToFrac $ accentDifference - scheme where -- The accent colours should be as different as possible. accentDifference = minimum $ do @@ -79,6 +75,12 @@ instance (Floating a, Real a) => Species (V.Vector (LAB a)) (V.Vector (LAB a)) w -- The accent colours should all have the given lightness. + sum (V.map (difference accentValue) $ accent lightnesses) + scheme = case polarity of + "either" -> min lightScheme darkScheme + "light" -> lightScheme + "dark" -> darkScheme + _ -> error ("Invalid polarity: " ++ polarity) + {- For light themes, the background is bright and the text is dark. The accent colours are slightly darker. diff --git a/stylix/palette.nix b/stylix/palette.nix index 46da7e51..fabdd5f7 100644 --- a/stylix/palette.nix +++ b/stylix/palette.nix @@ -7,7 +7,7 @@ let cfg = config.stylix; paletteJSON = pkgs.runCommand "palette.json" { } '' - ${palette-generator}/bin/palette-generator ${cfg.image} $out + ${palette-generator}/bin/palette-generator ${cfg.polarity} ${cfg.image} $out ''; palette = importJSON paletteJSON // { @@ -18,6 +18,18 @@ let in { options.stylix = { + polarity = mkOption { + type = types.enum [ "either" "light" "dark" ]; + default = "either"; + description = '' + Use this option to force a light or dark theme. + + By default we will select whichever is ranked better by the genetic + algorithm. This aims to get good contrast between the foreground and + background, as well as some variety in the highlight colours. + ''; + }; + image = mkOption { type = types.coercedTo types.package toString types.path; description = ''