{ pkgs, config, lib ? pkgs.lib }: let colors = config.colorScheme.palette; mediaImages = config.syscfg.media.main; mediaNames = map (image: builtins.baseNameOf (toString image)) mediaImages; dither = "atkinson"; # none | floyd-steinberg | atkinson | jjn | burkes | sierra | sierra-lite paletteSize = 0; hexChars = "0123456789abcdef"; hexMap = { "0" = 0; "1" = 1; "2" = 2; "3" = 3; "4" = 4; "5" = 5; "6" = 6; "7" = 7; "8" = 8; "9" = 9; "a" = 10; "b" = 11; "c" = 12; "d" = 13; "e" = 14; "f" = 15; }; baseColors = [ colors.base00 colors.base01 colors.base02 colors.base03 colors.base04 colors.base05 colors.base06 colors.base07 colors.base08 colors.base09 colors.base0A colors.base0B colors.base0C colors.base0D colors.base0E colors.base0F ]; round = x: builtins.floor (x + 0.5); clamp = x: if x < 0 then 0 else if x > 255 then 255 else x; parseHexByte = byte: let hi = hexMap.${builtins.substring 0 1 byte}; lo = hexMap.${builtins.substring 1 1 byte}; in hi * 16 + lo; hexToRgb = hex: let clean = lib.toLower (lib.removePrefix "#" hex); in { r = parseHexByte (builtins.substring 0 2 clean); g = parseHexByte (builtins.substring 2 2 clean); b = parseHexByte (builtins.substring 4 2 clean); }; componentToHex = value: let bounded = clamp value; hi = builtins.div bounded 16; lo = bounded - hi * 16; in "${builtins.substring hi 1 hexChars}${builtins.substring lo 1 hexChars}"; rgbToHex = color: "${componentToHex color.r}${componentToHex color.g}${componentToHex color.b}"; getTint = c: weight: round (c + (255 - c) * weight); getShade = c: weight: round (c * weight); tint = color: weight: { r = getTint color.r weight; g = getTint color.g weight; b = getTint color.b weight; }; shade = color: weight: { r = getShade color.r weight; g = getShade color.g weight; b = getShade color.b weight; }; genPalette = color: let tints = if paletteSize == 0 then [ ] else lib.genList (i: tint color ((i + 1.0) / paletteSize)) paletteSize; shades = if paletteSize == 0 then [ ] else lib.genList (i: shade color (i * 1.0 / paletteSize)) paletteSize; in lib.reverseList tints ++ [ color ] ++ lib.reverseList shades; keepColor = color: let sum = color.r + color.g + color.b; in sum > 0 && sum < 765; paletteColors = lib.concatMap (hex: lib.filter keepColor (genPalette (hexToRgb hex))) baseColors; paletteHex = lib.concatStringsSep "," (map rgbToHex paletteColors); gifPaletteFile = pkgs.writeText "wallpaper-gifpalette.txt" ( lib.concatMapStringsSep "\n" (color: "${toString color.r} ${toString color.g} ${toString color.b}") paletteColors ); buildCommands = lib.concatMapStringsSep "\n" (image: let source = toString image; name = builtins.baseNameOf source; target = "build/${name}"; in if lib.hasSuffix ".gif" (lib.toLower name) then '' gifsicle --use-colormap ${lib.escapeShellArg (toString gifPaletteFile)} < ${lib.escapeShellArg source} > ${lib.escapeShellArg target} '' else '' repalette ${lib.escapeShellArg source} ${lib.escapeShellArg target} -p ${lib.escapeShellArg paletteHex} --dither ${lib.escapeShellArg dither} '' ) mediaImages; in assert lib.assertMsg (builtins.length mediaNames == builtins.length (lib.unique mediaNames)) "syscfg.media.main contains duplicate basenames, which would collide in generated wallpaper output."; pkgs.stdenv.mkDerivation { pname = "generated-wallpaper"; version = "local"; dontUnpack = true; nativeBuildInputs = with pkgs; [ custom.repalette gifsicle ]; buildPhase = '' runHook preBuild mkdir -p build ${buildCommands} runHook postBuild ''; installPhase = '' runHook preInstall mkdir -p $out/share/wallpaper cp -r build/. $out/share/wallpaper/ runHook postInstall ''; }