diff --git a/.gitignore b/.gitignore
index b63eb088..f62f15eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
resources/demo.mp4
-test_pack/theme/
+test/theme/
*.egg-info/
__pycache__/
diff --git a/color_manager/utils.py b/color_manager/utils.py
index 88bbcfd2..e1cf7092 100644
--- a/color_manager/utils.py
+++ b/color_manager/utils.py
@@ -177,18 +177,23 @@ def generate_palette_dict(colors:List[str]) -> Dict[str,LabColor]:
return palette_dict
-def get_input_colors(resource) -> Tuple[List[str],bool,bool]:
- """ Returns an HSL tuple, or a list of colors, depending on the input, as well as a boolean indicating which one, as well as if the palettes specifies smoothing pngs/jpgs. """
+def get_input_colors(resource):
+ """ Returns an HSL tuple, or a palette of colors, or a color mapping, depending on the input, as well as a string indicating which one, and if smoothing should be applied to pngs/jpgs. """
# If resource is an hsl color.
if isinstance(resource, tuple) and len(resource) == 3:
- return resource, False, True
+ return resource, False, "color"
else:
- # If resource is a path to a palette.
- if type(resource) is str: resource = load_json_file(resource)
+ # If resource is a path to a resource, first unpack.
+ if type(resource) is str:
+ resource = load_json_file(resource)
- return generate_palette_dict(resource["colors"]), resource["smooth"], False
+ if resource["type"] == "palette":
+ return generate_palette_dict(resource["colors"]), resource["smooth"], "palette"
+
+ elif resource["type"] == "mapping":
+ return resource["map"], resource["smooth"], "mapping"
def get_file_colors(text:str) -> Set[str]:
""" Return a set of all unique colors within a given string representing an svg-file. """
@@ -307,7 +312,7 @@ def copy_pack(src_path:str, dest_path:str, name:str) -> str:
# Vector-based recoloring ------------------------------------------------------
-def apply_monotones_to_vec(svg:str, colors:Set[str], hsl:Tuple[float,float,float]) -> str:
+def apply_monotones_to_vec(text:str, colors:Set[str], hsl:Tuple[float,float,float]) -> str:
""" Replace every instance of color within the given list with their monochrome equivalent in the given string representing an svg-file, determined by the given hue, saturation and lightness offset. """
h, s, l_offset = hsl
@@ -315,7 +320,7 @@ def apply_monotones_to_vec(svg:str, colors:Set[str], hsl:Tuple[float,float,float
if s == 0:
for color in colors:
graytone = hex_to_gray(color)
- svg = re.sub(color, graytone, svg)
+ text = re.sub(color, graytone, text)
else:
l_offset = (l_offset - 0.5) * 2 # Remapping.
@@ -325,18 +330,27 @@ def apply_monotones_to_vec(svg:str, colors:Set[str], hsl:Tuple[float,float,float
l = (0.21*r + 0.72*g + 0.07*b)/255
l = max(-1, min(l+l_offset, 1))
monochrome = rgb_to_hex(hsl_to_rgb((h, s, l)))
- svg = re.sub(color, monochrome, svg)
+ text = re.sub(color, monochrome, text)
- return svg
+ return text
-def apply_palette_to_vec(svg:str, colors:Set[str], new_colors:Dict[str,LabColor]) -> str:
- """ Replace colors in a given svg/css with the closest match within a given color palette. """
+def apply_palette_to_vec(text:str, colors:Set[str], new_colors:Dict[str,LabColor]) -> str:
+ """ Replace hexadecimal color codes in a given svg/xml/css string with their closest matches within the given color palette. """
for color in colors:
new_color = closest_match(color, new_colors)
- svg = re.sub(color, new_color, svg)
+ text = re.sub(color, new_color, text)
- return svg
+ return text
+
+def apply_mapping_to_vec(text:str, colors:Set[str], map:Dict[str,str]) -> str:
+ """ Replace hexadecimal color codes in a given svg/xml/css string according to a given color mapping. """
+
+ for color in colors:
+ if color in map:
+ text = re.sub(color, map[color], text)
+
+ return text
# Pixel-based recoloring -------------------------------------------------------
@@ -382,8 +396,8 @@ def apply_palette_to_img(img:Image, new_colors:Dict[str,LabColor], smooth:bool)
rgb_palette = [(palette[i], palette[i+1], palette[i+2]) for i in range(0, len(palette), 3)]
hex_palette = ["#%02x%02x%02x" % rgb for rgb in rgb_palette]
- new_palette = []
+ new_palette = []
for color in hex_palette:
new_color = hex_to_rgb(closest_match(color, new_colors))
new_palette.extend(new_color)
@@ -391,6 +405,30 @@ def apply_palette_to_img(img:Image, new_colors:Dict[str,LabColor], smooth:bool)
img.putpalette(new_palette)
return img
+def apply_mapping_to_img(img:Image, map:Dict[str,str], smooth:bool) -> Image:
+ """ Replace colors in a given image according to a given mapping. """
+
+ #raise Exception("Function not yet implemented.")
+
+ if smooth: img = img.convert("P", palette=Image.ADAPTIVE, colors=256)
+ else: img = img.convert("P")
+
+ palette = img.getpalette()
+
+ rgb_palette = [(palette[i], palette[i+1], palette[i+2]) for i in range(0, len(palette), 3)]
+
+ hex_palette = ["#%02x%02x%02x" % rgb for rgb in rgb_palette]
+
+ new_palette = []
+ for color in hex_palette:
+ if color in map:
+ new_palette.extend(hex_to_rgb(map[color]))
+ else:
+ new_palette.extend(hex_to_rgb(color))
+
+ img.putpalette(new_palette)
+ return img
+
# User interface functions -----------------------------------------------------
def recolor(src_path:str, dest_path:str, name:str, replacement) -> None:
@@ -399,7 +437,7 @@ def recolor(src_path:str, dest_path:str, name:str, replacement) -> None:
check_path(src_path)
check_path(dest_path)
- new_colors, smooth, is_mono = get_input_colors(replacement)
+ new_colors, smooth, op = get_input_colors(replacement)
dest_path = copy_pack(src_path, dest_path, name)
# Recolor vector graphics.
@@ -410,8 +448,12 @@ def recolor(src_path:str, dest_path:str, name:str, replacement) -> None:
x = expand_all_hex(x)
colors = get_file_colors(x)
- if is_mono: x = apply_monotones_to_vec(x, colors, new_colors)
- else: x = apply_palette_to_vec(x, colors, new_colors)
+ if op == "color":
+ x = apply_monotones_to_vec(x, colors, new_colors)
+ elif op == "palette":
+ x = apply_palette_to_vec(x, colors, new_colors)
+ elif op == "mapping":
+ x = apply_mapping_to_vec(x, colors, new_colors)
with open(path, 'w') as file: file.write(x)
@@ -424,8 +466,12 @@ def recolor(src_path:str, dest_path:str, name:str, replacement) -> None:
x = expand_all_hex(x)
colors = get_file_colors(x)
- if is_mono: x = apply_monotones_to_vec(x, colors, new_colors)
- else: x = apply_palette_to_vec(x, colors, new_colors)
+ if op == "color":
+ x = apply_monotones_to_vec(x, colors, new_colors)
+ elif op == "palette":
+ x = apply_palette_to_vec(x, colors, new_colors)
+ elif op == "mapping":
+ x = apply_mapping_to_vec(x, colors, new_colors)
with open(path, 'w') as file: file.write(x)
@@ -436,8 +482,12 @@ def recolor(src_path:str, dest_path:str, name:str, replacement) -> None:
x = x.convert("RGBA")
a = x.split()[3] # Save original alpha channel.
- if is_mono: x = apply_monotones_to_img(x, new_colors)
- else: x = apply_palette_to_img(x, new_colors, smooth)
+ if op == "color":
+ x = apply_monotones_to_img(x, new_colors)
+ elif op == "palette":
+ x = apply_palette_to_img(x, new_colors, smooth)
+ elif op == "mapping":
+ x = apply_mapping_to_img(x, new_colors, smooth)
x = x.convert("RGBA")
r,g,b,_ = x.split()
@@ -450,8 +500,12 @@ def recolor(src_path:str, dest_path:str, name:str, replacement) -> None:
x = Image.open(path)
x = x.convert("RGB")
- if is_mono: x = apply_monotones_to_img(x, new_colors)
- else: x = apply_palette_to_img(x, new_colors, smooth)
+ if op == "color":
+ x = apply_monotones_to_img(x, new_colors)
+ elif op == "palette":
+ x = apply_palette_to_img(x, new_colors, smooth)
+ elif op == "mapping":
+ x = apply_mapping_to_img(x, new_colors, smooth)
x = x.convert("RGB")
x.save(path)
diff --git a/mappings/renord.json b/mappings/renord.json
new file mode 100644
index 00000000..9abe9269
--- /dev/null
+++ b/mappings/renord.json
@@ -0,0 +1,14 @@
+{
+ "type": "mapping",
+ "name": "Re-Nord",
+ "desc": "Maps nord colors to different nord colors. Only meant for testing the color remapping function.",
+ "smooth": true,
+ "map": {
+ "#434c5e": "#2e3440",
+ "#4c566a": "#3b4252",
+ "#b48ead": "#8fbcbb",
+ "#ebcb8b": "#eceff4",
+ "#d08770": "#e5e9f0",
+ "#bf616a": "#d8dee9"
+ }
+}
diff --git a/palettes/catppuccin.json b/palettes/catppuccin.json
index f718162f..9e7bf058 100644
--- a/palettes/catppuccin.json
+++ b/palettes/catppuccin.json
@@ -1,4 +1,5 @@
{
+ "type": "palette",
"name": "Catppuccin Latte",
"desc": "Pastel theme that aims to be the middle ground between low and high contrast themes.",
"smooth": false,
diff --git a/palettes/dracula.json b/palettes/dracula.json
index f40ef99d..889a5040 100644
--- a/palettes/dracula.json
+++ b/palettes/dracula.json
@@ -1,6 +1,7 @@
{
+ "type": "palette",
"name": "Dracula",
- "desc": "Dark mode color scheme with vibrant and contrasting colors.",
+ "desc": "Dark mode scheme with vibrant and contrasting colors.",
"smooth": false,
"colors": [
"#44475a",
diff --git a/palettes/nord.json b/palettes/nord.json
index 27b8b1cd..c7f24942 100644
--- a/palettes/nord.json
+++ b/palettes/nord.json
@@ -1,4 +1,5 @@
{
+ "type": "palette",
"name": "Nord",
"desc": "An arctic, north-bluish color palette.",
"smooth": true,
diff --git a/palettes/one_dark.json b/palettes/one_dark.json
index d59df951..7a15bf52 100644
--- a/palettes/one_dark.json
+++ b/palettes/one_dark.json
@@ -1,4 +1,5 @@
{
+ "type": "palette",
"name": "One Dark",
"desc": "Dark grey background with vibrant colors.",
"smooth": true,
diff --git a/readme.md b/readme.md
index 00b74f1e..f70b460d 100644
--- a/readme.md
+++ b/readme.md
@@ -20,8 +20,11 @@ Color Manager is a program for recoloring and manipulating existing icon packs,
| **Original** |  |
| **Monochrome**:
`(0.6,0.54,0.5)` |  |
| **Multichrome**:
`nord.json`
`smooth=false` |  |
-| **Multichrome**:
`nord.json`
`smooth=true` |  |
-| **Extraction**:
Original `num=10` |  |
+| **Multichrome**:
`nord.json`
`smooth=true` |  |
+| **Remapping**:
`renord.json`
Remapped
previous to
alterate nord
colors. |  |
+| **Extraction**:
Original `num=10` |  |
+
+With these tools, one can build a pipeline to customize most assets to fit into a larger design.
**GUI Demo**:

@@ -41,7 +44,7 @@ Color Manager is a program for recoloring and manipulating existing icon packs,
- [x] Remove metadata from svgs.
- [x] Adding basic geometry to the backgrounds of svg icons.
- [x] Preserve transparency in pngs after multichrome recoloring.
-- [ ] Add an option to map colors directly using a json dictionary.
+- [x] Add function to remap colors directly using a json dictionary.
- [ ] Optional automatic palette extending.
- [ ] Basic framework for manipulating GTK, Cinnamon and Metacity themes.
- [ ] Intelligent color inversion.
@@ -73,25 +76,26 @@ from color_manager import utils
```
Recoloring collections:
```python
-src = "test_pack"
+src = "test/graphics" # Also try "test/theme"
name = "my_pack"
dest = "~/Downloads"
-hsl = (0.5, 0.5, 0.5) # = rc.norm_hsl(180, 50, 50)
-palette = "palettes/dracula.json"
+color = (0.6, 0.54, 0.5) # = rc.normalize_hsl(180, 50, 50)
+palette = "palettes/nord.json"
+mapping = "mappings/renord.json"
-utils.recolor(src, dest, name, hsl) # Either hsl or palette.
+utils.recolor(src, dest, name, color) # Either color, palette, or mapping.
```
Extracting color palette:
```python
-image = "test_pack/imgs/lake_cabin.png" # Also try an svg.
-num_colors = 10
+image = "test/graphics/imgs/lake_cabin.png" # Also try an svg.
+num_colors = 10 # May be any number.
output = "resources/palette.png" # Optional - saves colors as image.
utils.extract_colors(image, num_colors, output)
```
Adding backdrops to svg icons:
```python
-src = "test_pack"
+src = "test/graphics"
name = "my_pack"
dest = "~/Downloads"
color = "#000000" # Optional - Defaults to black.
diff --git a/resources/wallpaper/remapped.png b/resources/wallpaper/remapped.png
new file mode 100644
index 00000000..f9c1fd21
Binary files /dev/null and b/resources/wallpaper/remapped.png differ
diff --git a/test.ipynb b/test.ipynb
index d99db35f..10acc233 100644
--- a/test.ipynb
+++ b/test.ipynb
@@ -2,18 +2,9 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 1,
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "The autoreload extension is already loaded. To reload it, use:\n",
- " %reload_ext autoreload\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2\n",
@@ -23,31 +14,43 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 26,
"metadata": {},
"outputs": [
{
- "ename": "Exception",
- "evalue": "Invalid path: ~/Downloads",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
- "\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
- "Cell \u001b[0;32mIn[10], line 7\u001b[0m\n\u001b[1;32m 4\u001b[0m hsl \u001b[39m=\u001b[39m (\u001b[39m0.6\u001b[39m, \u001b[39m0.54\u001b[39m, \u001b[39m0.5\u001b[39m) \u001b[39m# = rc.normalize_hsl(180, 50, 50)\u001b[39;00m\n\u001b[1;32m 5\u001b[0m palette \u001b[39m=\u001b[39m \u001b[39m\"\u001b[39m\u001b[39mpalettes/nord.json\u001b[39m\u001b[39m\"\u001b[39m\n\u001b[0;32m----> 7\u001b[0m utils\u001b[39m.\u001b[39;49mrecolor(src, dest, name, hsl) \u001b[39m# Either hsl or palette\u001b[39;00m\n",
- "File \u001b[0;32m~/Projects/Color_manager/color_manager/utils.py:402\u001b[0m, in \u001b[0;36mrecolor\u001b[0;34m(src_path, dest_path, name, replacement)\u001b[0m\n\u001b[1;32m 399\u001b[0m \u001b[39m\u001b[39m\u001b[39m\"\"\" Recursively copies and converts a source folder into a destination, given either an hsl color, a palette, or a color mapping. \"\"\"\u001b[39;00m\n\u001b[1;32m 401\u001b[0m check_path(src_path)\n\u001b[0;32m--> 402\u001b[0m check_path(dest_path)\n\u001b[1;32m 404\u001b[0m new_colors, smooth, is_mono \u001b[39m=\u001b[39m get_input_colors(replacement)\n\u001b[1;32m 405\u001b[0m dest_path \u001b[39m=\u001b[39m copy_pack(src_path, dest_path, name)\n",
- "File \u001b[0;32m~/Projects/Color_manager/color_manager/utils.py:30\u001b[0m, in \u001b[0;36mcheck_path\u001b[0;34m(path)\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mcheck_path\u001b[39m(path:\u001b[39mstr\u001b[39m) \u001b[39m-\u001b[39m\u001b[39m>\u001b[39m \u001b[39mNone\u001b[39;00m:\n\u001b[1;32m 29\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m os\u001b[39m.\u001b[39mpath\u001b[39m.\u001b[39mexists(path):\n\u001b[0;32m---> 30\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mException\u001b[39;00m(\u001b[39m\"\u001b[39m\u001b[39mInvalid path: \u001b[39m\u001b[39m\"\u001b[39m \u001b[39m+\u001b[39m path)\n",
- "\u001b[0;31mException\u001b[0m: Invalid path: ~/Downloads"
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "/home/nv/Downloads/my_pack/index.theme\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "svg: 100%|██████████| 6/6 [00:00<00:00, 1582.36it/s]\n",
+ "png: 0%| | 0/8 [00:00, ?it/s]"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "png: 100%|██████████| 8/8 [00:11<00:00, 1.46s/it]\n",
+ "jpg: 100%|██████████| 1/1 [00:04<00:00, 4.32s/it]\n"
]
}
],
"source": [
- "src = \"test_pack\" # = \"test_theme\"\n",
- "name = \"my_pack3\" # = \"my_theme\"\n",
+ "src = \"test/graphics\" # Also try \"test/theme\"\n",
+ "name = \"my_pack\"\n",
"dest = \"~/Downloads\"\n",
- "hsl = (0.6, 0.54, 0.5) # = rc.normalize_hsl(180, 50, 50)\n",
+ "color = (0.6, 0.54, 0.5) # = rc.normalize_hsl(180, 50, 50)\n",
"palette = \"palettes/nord.json\"\n",
+ "mapping = \"mappings/renord.json\"\n",
"\n",
- "utils.recolor(src, dest, name, hsl) # Either hsl or palette"
+ "# Either input a single color, palette, or mapping.\n",
+ "utils.recolor(src, dest, name, color)"
]
}
],
diff --git a/test_pack/index.theme b/test/graphics/index.theme
similarity index 100%
rename from test_pack/index.theme
rename to test/graphics/index.theme
diff --git a/test_pack/links/firefox.svg b/test/graphics/links/firefox.svg
similarity index 100%
rename from test_pack/links/firefox.svg
rename to test/graphics/links/firefox.svg
diff --git a/test_pack/pngs/account.png b/test/graphics/pngs/account.png
similarity index 100%
rename from test_pack/pngs/account.png
rename to test/graphics/pngs/account.png
diff --git a/test_pack/pngs/colors.png b/test/graphics/pngs/colors.png
similarity index 100%
rename from test_pack/pngs/colors.png
rename to test/graphics/pngs/colors.png
diff --git a/test_pack/pngs/firefox.png b/test/graphics/pngs/firefox.png
similarity index 100%
rename from test_pack/pngs/firefox.png
rename to test/graphics/pngs/firefox.png
diff --git a/test_pack/pngs/git.png b/test/graphics/pngs/git.png
similarity index 100%
rename from test_pack/pngs/git.png
rename to test/graphics/pngs/git.png
diff --git a/test_pack/pngs/video.png b/test/graphics/pngs/video.png
similarity index 100%
rename from test_pack/pngs/video.png
rename to test/graphics/pngs/video.png
diff --git a/test_pack/pngs/vscode.png b/test/graphics/pngs/vscode.png
similarity index 100%
rename from test_pack/pngs/vscode.png
rename to test/graphics/pngs/vscode.png
diff --git a/test_pack/svgs/account.svg b/test/graphics/svgs/account.svg
similarity index 100%
rename from test_pack/svgs/account.svg
rename to test/graphics/svgs/account.svg
diff --git a/test_pack/svgs/colors.svg b/test/graphics/svgs/colors.svg
similarity index 100%
rename from test_pack/svgs/colors.svg
rename to test/graphics/svgs/colors.svg
diff --git a/test_pack/svgs/firefox.svg b/test/graphics/svgs/firefox.svg
similarity index 100%
rename from test_pack/svgs/firefox.svg
rename to test/graphics/svgs/firefox.svg
diff --git a/test_pack/svgs/git.svg b/test/graphics/svgs/git.svg
similarity index 100%
rename from test_pack/svgs/git.svg
rename to test/graphics/svgs/git.svg
diff --git a/test_pack/svgs/video.svg b/test/graphics/svgs/video.svg
similarity index 100%
rename from test_pack/svgs/video.svg
rename to test/graphics/svgs/video.svg
diff --git a/test_pack/svgs/vscode.svg b/test/graphics/svgs/vscode.svg
similarity index 100%
rename from test_pack/svgs/vscode.svg
rename to test/graphics/svgs/vscode.svg
diff --git a/test_pack/wallpapers/lake_cabin.jpg b/test/graphics/wallpapers/lake_cabin.jpg
similarity index 100%
rename from test_pack/wallpapers/lake_cabin.jpg
rename to test/graphics/wallpapers/lake_cabin.jpg
diff --git a/test_pack/wallpapers/lake_cabin.png b/test/graphics/wallpapers/lake_cabin.png
similarity index 100%
rename from test_pack/wallpapers/lake_cabin.png
rename to test/graphics/wallpapers/lake_cabin.png
diff --git a/test/graphics/wallpapers/lake_cabin_nord.png b/test/graphics/wallpapers/lake_cabin_nord.png
new file mode 100644
index 00000000..a1a7b000
Binary files /dev/null and b/test/graphics/wallpapers/lake_cabin_nord.png differ