├── .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 | logo 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 | ![dash-pylette](https://github.com/AnnMarieW/dash-pylette/assets/72614349/1ce897c4-caa2-4b24-a0a7-d795faa516d5) 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 --------------------------------------------------------------------------------