├── .gitignore
├── LICENSE
├── README.md
├── app.py
├── assets
├── app.png
├── bird.jpg
├── dash-pylett-logo-192x192.jpg
├── dash-pylette-logo.jpg
├── duck.jpg
├── fall.jpg
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── flowers.jpg
├── flowers_arrangement.jpg
├── mushrooms.jpg
├── mycss.css
└── space_needle.jpg
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.ipynb_notebooks
2 | *.ipynb
3 |
4 | ignore
5 | *.csv
6 | *.log
7 | .DS_Store
8 |
9 | # Environments
10 | .env
11 | .venv
12 | env/
13 | venv/
14 | ENV/
15 | env.bak/
16 | venv.bak/
17 | vv
18 |
19 | # IDE
20 | .idea/*
21 | .vscode/*
22 |
23 | # Byte-compiled / optimized / DLL files
24 | __pycache__/
25 | *.py[cod]
26 | *$py.class
27 | .mypy_cache/
28 |
29 | # Unit test / coverage reports
30 | htmlcov/
31 | .tox/
32 | .nox/
33 | .coverage
34 | .coverage.*
35 | .cache
36 | nosetests.xml
37 | coverage.xml
38 | *.cover
39 | .hypothesis/
40 | .pytest_cache/
41 |
42 | # Distribution / packaging
43 | .Python
44 | build/
45 | dash/deps/
46 | dash/html/*
47 | !dash/html/.gitkeep
48 | dash/dcc/*
49 | !dash/dcc/.gitkeep
50 | dash/dash_table/*
51 | !dash/dash_table/.gitkeep
52 | develop-eggs/
53 | dist/
54 | downloads/
55 | eggs/
56 | .eggs/
57 | lib/
58 | lib64/
59 | parts/
60 | sdist/
61 | var/
62 | wheels/
63 | pip-wheel-metadata/
64 | share/python-wheels/
65 | *.egg-info/
66 | .installed.cfg
67 | *.egg
68 | MANIFEST
69 | node_modules/
70 | .npm
71 | npm-debug*
72 |
73 | dash_generator_test_component_standard/
74 | dash_generator_test_component_nested/
75 | dash_test_components/
76 | dash_generator_test_component_typescript/
77 | inst/
78 | man/
79 | R/
80 | .Rbuildignore
81 | .Rdata
82 | .Rhistory
83 | DESCRIPTION
84 | NAMESPACE
85 | digest.json
86 | VERSION.txt
87 |
88 | # vim
89 | *.swp
90 |
91 | # Exceptions
92 | !components/dash-core-components/tests/integration/upload/upload-assets/upft001.csv
93 | !components/dash-table/tests/assets/*.csv
94 | !components/dash-table/tests/selenium/assets/*.csv
95 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Ann Marie Ward
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dash Pylette
5 | Create color palettes from images
6 |
7 |
8 |
9 |
10 |
11 |
12 | ### Introduction
13 |
14 | Welcome to Dash Pylette - a Dash app showcasing the power of the [Pylette](https://github.com/qTipTip/Pylette) library. This app serves as a tool to extract
15 | color palettes from images. Whether you're a designer, artist, or developer, Dash Pylette provides an easy
16 | way to generate color schemes for your projects.
17 |
18 | ### See it live at https://dashpylette.pythonanywhere.com/
19 |
20 | ------------------
21 |
22 | 
23 |
24 |
25 | --------------
26 |
27 | ### Getting Started
28 |
29 | 1. **Select Image**: Choose an image from the dropdown menu or upload your own.
30 |
31 | Configure Pylette
32 |
33 | 2. **Sort Colors By** - will sort the color palette by the frequency or the luminance (perceived brightness).
34 | 3. **Color Quantization Mode** - to specify the color quantization method - either using K-Means (default) or Median-Cut algorithms.
35 | 4. **Palette Color Count** - set the number of colors in the palette
36 | 5. **Resize** - resize the image to a more manageable size before beginning color extraction. This significantly speeds up the extraction, but reduces the faithfulness of the color palette.
37 | 6. **Generate Palette**: Let Pylette work its magic and extract a color palette from the selected image.
38 |
39 | Use the generated Color Palette
40 |
41 | 7. **Explore**: Explore the extracted colors and pick your favorites using the color picker.
42 | 8. **Set Frame Color**: Set the selected color as the frame color for the image to see it in action.
43 | 9. **Experiment**: Try out different images, algorithms, and sorting methods to discover unique color combinations.
44 | 10. **Copy the Color Palette**: Use the generated color palette in your onw project!
45 |
46 |
47 | ### To run the app locally
48 |
49 | To get started with Dash Pylette, clone this repository and run the app:
50 |
51 | ```bash
52 | git clone https://github.com/AnnMarieW/dash-pylette.git
53 | cd dash-pylette
54 | python3 -m venv venv && . venv/bin/activate
55 | pip install -r requirements.txt
56 | python3 app.py
57 | ```
58 |
59 | ### Contributing
60 |
61 | Contributions to Dash Pylette are welcome! Whether it's fixing bugs, adding features, or improving documentation,
62 | your contributions help make Dash Pylette even better. Simply fork the repository, make your changes, and submit a pull request.
63 |
64 | ### Show Your Support!
65 | If you find Dash Pylette helpful or enjoy using the open-source libraries it depends on, please consider showing your support by giving a ⭐️ star to:
66 |
67 | - Dash Pylette: Help us grow and improve by giving this repository a star.
68 | - [Pylette](https://github.com/qTipTip/Pylette): Show appreciation for the powerful color extraction capabilities of Pylette.
69 | - [Dash](https://github.com/plotly/dash): Support the development of Dash, the Python framework used to create interactive web applications.
70 | - [Dash Mantine Components](https://github.com/snehilvj/dash-mantine-components): Contribute to the growth of Dash Mantine Components, providing beautiful UI components for Dash apps.
71 | - [DashIconify](https://github.com/snehilvj/dash-iconify): Encourage the development of DashIconify, enabling the use of icons in Dash applications with ease.
72 | Your stars not only boost our morale but also help others discover these valuable tools. Thank you for your support!
73 |
74 | ### License
75 |
76 | Dash Pylette is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
77 |
78 | ---
79 |
80 | #### **Happy Palette Crafting!** 🎨✨
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | import dash
2 | import dash_mantine_components as dmc
3 | from dash_iconify import DashIconify
4 | from dash import Dash, _dash_renderer, html, dcc, callback, Input, Output, State, ctx
5 | from Pylette import extract_colors
6 | import tempfile
7 | import base64
8 |
9 | _dash_renderer._set_react_version("18.2.0")
10 |
11 | icons = {
12 | "github": "ion:logo-github",
13 | "tools": "bi:tools",
14 | "palette": "bi:palette",
15 | }
16 |
17 |
18 | select_image = dmc.Select(
19 | id="select-image",
20 | label="Select sample image",
21 | placeholder="Select one",
22 | data=[
23 | {"value": "assets/flowers.jpg", "label": "Flowers"},
24 | {"value": "assets/fall.jpg", "label": "Fall in New England"},
25 | {"value": "assets/duck.jpg", "label": "Duck"},
26 | {"value": "assets/space_needle.jpg", "label": "Space Needle"},
27 | {"value": "assets/mushrooms.jpg", "label": "Mushrooms"},
28 | {"value": "assets/flowers_arrangement.jpg", "label": "Flower Arrangement"},
29 | ],
30 | w="100%",
31 | mb=10,
32 | value="assets/flowers.jpg",
33 | clearable=True,
34 | )
35 |
36 | upload = dcc.Upload(
37 | dmc.NavLink(label="Drag and Drop or Upload an Image File", className="upload"),
38 | multiple=False,
39 | id="upload-image",
40 | )
41 |
42 |
43 | sort_by_data = [["frequency", "Frequency"], ["luminance", "Luminance"]]
44 | sort_by = dmc.RadioGroup(
45 | children=dmc.Group([dmc.Radio(l, value=k) for k, l in sort_by_data], my=10),
46 | id="radio-group-sort-by",
47 | value="frequency",
48 | label="Sort colors by",
49 | size="sm",
50 | mb=30,
51 | )
52 |
53 |
54 | mode_data = [["KM", "K-Means"], ["MC", "Median Cut"]]
55 | mode = dmc.RadioGroup(
56 | children=dmc.Group([dmc.Radio(l, value=k) for k, l in mode_data], my=10),
57 | id="radio-group-mode",
58 | value="MC",
59 | label="Color quantization mode",
60 | size="sm",
61 | mb=30,
62 | )
63 |
64 |
65 | resize = dmc.Checkbox(
66 | id="checkbox-resize", label="Resize image for faster results", checked=True, mb=30
67 | )
68 |
69 |
70 | pallette_color_count = dmc.Slider(
71 | value=6, min=1, step=1, max=12, w="100%", my=20, id="color-count"
72 | )
73 |
74 |
75 | set_frame_color = dmc.Checkbox(
76 | id="checkbox-set-background", label="Set image frame color", checked=True, mb=10
77 | )
78 |
79 |
80 | color_picker = dmc.ColorPicker(
81 | id="color-picker", swatchesPerRow=6, withPicker=False, mb=10
82 | )
83 |
84 |
85 | def create_link(icon, href, text=""):
86 | return dmc.Anchor(
87 | [
88 | (
89 | dmc.ActionIcon(
90 | DashIconify(icon=icon, width=25), variant="transparent", size="lg"
91 | )
92 | if icon
93 | else None
94 | ),
95 | text,
96 | ],
97 | href=href,
98 | target="_blank",
99 | )
100 |
101 |
102 | burger_button = dcc.Loading(
103 | dmc.Burger(id="burger-button", opened=False, hiddenFrom="md"),
104 | overlay_style={"zIndex": 5000},
105 | delay_show=500,
106 | custom_spinner=dmc.Group(dmc.Loader(type="dots", size="sm")),
107 | )
108 |
109 | header = dmc.Group(
110 | [
111 | burger_button,
112 | dmc.Image(src="/assets/dash-pylette-logo.jpg", h=36, w="100%"),
113 | dmc.Text(["Dash Pylette"], size="xl", fw=700),
114 | dmc.Text(" Get a color palette from an image", visibleFrom="sm", size="xl"),
115 | dmc.Text(create_link(icons["github"], "https://github.com/AnnMarieW/dash-pylette"), ml="auto")
116 | ],
117 | justify="flex-start",
118 | gap="sm",
119 | style={'height': '1 !important'}
120 | )
121 |
122 |
123 | def make_divider(text, icon):
124 | return dmc.Divider(
125 | label=[
126 | DashIconify(icon=icon, height=23),
127 | dmc.Text(text, ml=5, size="sm"),
128 | ],
129 | labelPosition="left",
130 | mt=60,
131 | mb=10,
132 | )
133 |
134 |
135 |
136 | navbar = dcc.Loading(
137 | dmc.ScrollArea(
138 | [
139 | dmc.Text("Get a color palette from an image", hiddenFrom="sm", fw=700),
140 | select_image,
141 | dmc.Text("Or upload a file", size="sm"),
142 | upload,
143 | make_divider("Configure Pylette", icons["tools"]),
144 | sort_by,
145 | mode,
146 | resize,
147 | dmc.Text("Palette color count", size="sm"),
148 | pallette_color_count,
149 | # Used to trigger a dcc.Loading component when page content is being updated
150 | dcc.Store(id="loading-trigger", data={}),
151 | ],
152 | offsetScrollbars=True,
153 | type="scroll",
154 | style={"height": "100%"},
155 | ),
156 | delay_show=500,
157 | custom_spinner=dmc.Loader(type="dots"),
158 | )
159 |
160 | image = html.Img(
161 | id="image",
162 | style={
163 | "height": "100%",
164 | "width": "100%",
165 | "objectFit": "contain",
166 | "padding": 50,
167 | },
168 | )
169 |
170 | page_content = dcc.Loading(
171 | [
172 | dmc.Center(dmc.Paper(image, id="image-card", h={"base": 400, "md": 600})),
173 | dmc.Group(id="palette", gap=0, justify="center", mt=10),
174 | html.Center(
175 | html.Div(
176 | [
177 | color_picker,
178 | dmc.Text(id="selected-color", mb=24),
179 | dmc.ScrollArea(
180 | html.Div(id="copy"),
181 | offsetScrollbars=True,
182 | type="scroll",
183 | style={"width": "100%"},
184 | ),
185 | ]
186 | )
187 | ),
188 | ],
189 | id="page-content",
190 | overlay_style={
191 | "visibility": "visible",
192 | "opacity": 0.3,
193 | },
194 | delay_show=500,
195 | custom_spinner=dmc.Group(
196 | [dmc.Text("Creating Palette", fw=700, size="xl"), dmc.Loader(type="dots")], bg="rgb(174, 178, 185)", p=12
197 | ),
198 | )
199 |
200 | # using pages just for an easy way to generate the meta-tags
201 | app = Dash(use_pages=True, pages_folder="")
202 |
203 |
204 | app_shell = dmc.AppShell(
205 | [
206 | dmc.AppShellHeader([dmc.Space(h=5), header], px=25, style={'height': '50px'}),
207 | dmc.AppShellNavbar(navbar, p=24, style={'top': '50px'}),
208 | dmc.AppShellMain(page_content),
209 | dcc.Store(id="select-image-store", data={}),
210 | ],
211 | header={"height": 70},
212 | padding="xl",
213 | navbar={
214 | "width": 375,
215 | "breakpoint": "md",
216 | "collapsed": {"mobile": True},
217 | },
218 | id="app-shell",
219 | )
220 |
221 | dash.register_page(
222 | "Dash Pylette",
223 | layout=app_shell,
224 | path="/",
225 | description="Welcome to Dash Pylette - a Dash app showcasing the power of the Pylette library. This app serves as a tool to extract color palettes from images. Whether you're a designer, artist, or developer, Dash Pylette provides an easy way to generate color schemes for your projects.",
226 | )
227 | app.layout = dmc.MantineProvider([dash.page_container])
228 |
229 |
230 | def save_base64_image(base64_string):
231 | """
232 | Save uploaded image to a temporary file
233 | :param base64_string: image
234 | :return: temporary path name
235 | """
236 | # Decode the base64 string into bytes
237 | image_data = base64.b64decode(base64_string.split(",")[1])
238 | # Create a temporary file
239 | with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as temp_file:
240 | # Write the image data to the temporary file
241 | temp_file.write(image_data)
242 | return temp_file.name
243 |
244 |
245 | @callback(
246 | Output("image", "src"),
247 | Output("select-image-store", "data"),
248 | Output("select-image", "value"),
249 | Input("select-image", "value"),
250 | Input("upload-image", "contents"),
251 | )
252 | def update_image(image_path, upload):
253 | """
254 | Notes:
255 | `extract_colors` accepts an image file path only. If a file is uploaded, it's saved in a temporary file
256 | the html.Img `src` prop is either the uploaded base64 file, or a path to a file in the assets folder.
257 | """
258 | if ctx.triggered_id == "upload-image":
259 | temp_file_path = save_base64_image(upload)
260 | return upload, temp_file_path, None
261 | if image_path == None:
262 | return dash.no_update, dash.no_update, dash.no_update
263 | return image_path, image_path, dash.no_update
264 |
265 |
266 | @callback(
267 | Output("color-picker", "swatches"),
268 | Output("color-picker", "value"),
269 | Output("copy", "children"),
270 | # triggers the loading component in the sidebar
271 | Output("loading-trigger", "data"),
272 | # triggers the loading component over the burger button in mobile
273 | Output("burger-button", "style"),
274 | Input("select-image-store", "data"),
275 | Input("color-count", "value"),
276 | Input("radio-group-sort-by", "value"),
277 | Input("radio-group-mode", "value"),
278 | Input("checkbox-resize", "checked"),
279 | )
280 | def process_image_and_update_layout(
281 | image_path, color_count, sort_value, mode_value, resize_value
282 | ):
283 | palette = extract_colors(
284 | image=image_path,
285 | palette_size=color_count,
286 | mode=mode_value,
287 | sort_mode=sort_value,
288 | resize=resize_value,
289 | )
290 |
291 | swatches = [f"rgb{color.rgb}" for color in palette]
292 | dominant_color = swatches[0]
293 |
294 | copy_swatches = dmc.CodeHighlight(
295 | code=f" color_palette = {swatches}",
296 | language="python",
297 | copyLabel="copy color palette",
298 | p="sm",
299 | style={"textAlign": "left"},
300 | )
301 | return swatches, dominant_color, copy_swatches, "show loading in navbar", {}
302 |
303 |
304 | @callback(
305 | Output("image-card", "style"),
306 | Output("selected-color", "children"),
307 | Input("color-picker", "value"),
308 | )
309 | def update_frame_color(color):
310 | style = {"backgroundColor": color}
311 | selected = f"Selected frame color: {color}"
312 | return style, selected
313 |
314 |
315 | @callback(
316 | Output("app-shell", "navbar"),
317 | Input("burger-button", "opened"),
318 | State("app-shell", "navbar"),
319 | )
320 | def navbar_is_open(opened, navbar):
321 | navbar["collapsed"] = {"mobile": not opened}
322 | return navbar
323 |
324 |
325 | # on mobile close the navbar on update
326 | @callback(
327 | Output("burger-button", "opened"),
328 | Input("select-image-store", "data"),
329 | Input("color-count", "value"),
330 | Input("radio-group-sort-by", "value"),
331 | Input("radio-group-mode", "value"),
332 | Input("checkbox-resize", "checked"),
333 | Input("select-image-store", "data"),
334 | prevent_initial_call=True,
335 | )
336 | def navbar_is_open(*_):
337 | return False
338 |
339 |
340 | if __name__ == "__main__":
341 | app.run_server(debug=True)
342 |
--------------------------------------------------------------------------------
/assets/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/app.png
--------------------------------------------------------------------------------
/assets/bird.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/bird.jpg
--------------------------------------------------------------------------------
/assets/dash-pylett-logo-192x192.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/dash-pylett-logo-192x192.jpg
--------------------------------------------------------------------------------
/assets/dash-pylette-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/dash-pylette-logo.jpg
--------------------------------------------------------------------------------
/assets/duck.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/duck.jpg
--------------------------------------------------------------------------------
/assets/fall.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/fall.jpg
--------------------------------------------------------------------------------
/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/favicon.ico
--------------------------------------------------------------------------------
/assets/flowers.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/flowers.jpg
--------------------------------------------------------------------------------
/assets/flowers_arrangement.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/flowers_arrangement.jpg
--------------------------------------------------------------------------------
/assets/mushrooms.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/mushrooms.jpg
--------------------------------------------------------------------------------
/assets/mycss.css:
--------------------------------------------------------------------------------
1 |
2 | .upload {
3 | width: 100%;
4 | height: 60px;
5 | line-eight: 60px;
6 | border-width: .8px;
7 | border-style: dashed;
8 | border-radius: 5px;
9 | text-align: center;
10 | }
--------------------------------------------------------------------------------
/assets/space_needle.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AnnMarieW/dash-pylette/fc1db0eedb7f7a678e5a42e3e371da8ff3dcbe8f/assets/space_needle.jpg
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | dash==2.17.0
2 | dash-mantine-components==0.14.3
3 | dash-iconify==0.1.2
4 | pylette==2.0.1
5 | # this version was required when hosting at pythonanywhere https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/2552
6 | scipy==1.9.1
--------------------------------------------------------------------------------