@rog0x/mcp-color-tools 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +229 -0
- package/dist/tools/color-converter.d.ts +56 -0
- package/dist/tools/color-converter.js +269 -0
- package/dist/tools/color-mixer.d.ts +22 -0
- package/dist/tools/color-mixer.js +119 -0
- package/dist/tools/contrast-checker.d.ts +27 -0
- package/dist/tools/contrast-checker.js +58 -0
- package/dist/tools/css-gradient.d.ts +23 -0
- package/dist/tools/css-gradient.js +96 -0
- package/dist/tools/palette-generator.d.ts +22 -0
- package/dist/tools/palette-generator.js +86 -0
- package/package.json +38 -0
- package/src/index.ts +264 -0
- package/src/tools/color-converter.ts +311 -0
- package/src/tools/color-mixer.ts +165 -0
- package/src/tools/contrast-checker.ts +80 -0
- package/src/tools/css-gradient.ts +143 -0
- package/src/tools/palette-generator.ts +126 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# mcp-color-tools
|
|
2
|
+
|
|
3
|
+
MCP server providing color manipulation and design tools for AI agents.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
### color_convert
|
|
8
|
+
|
|
9
|
+
Convert a color between HEX, RGB, HSL, HSV, and CMYK. Accepts any format as input and returns all formats.
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{ "color": "#ff6b35" }
|
|
13
|
+
{ "color": "rgb(255, 107, 53)" }
|
|
14
|
+
{ "color": "hsl(16, 100%, 60%)" }
|
|
15
|
+
{ "color": "hsv(16, 79%, 100%)" }
|
|
16
|
+
{ "color": "cmyk(0, 58, 79, 0)" }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### palette_generate
|
|
20
|
+
|
|
21
|
+
Generate a color palette from a base color.
|
|
22
|
+
|
|
23
|
+
**Types:** complementary, analogous, triadic, split-complementary, monochromatic
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{ "color": "#ff6b35", "type": "triadic" }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### contrast_check
|
|
30
|
+
|
|
31
|
+
Check WCAG contrast ratio between two colors. Reports AA/AAA compliance for normal text, large text, and UI components.
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{ "color1": "#ffffff", "color2": "#333333" }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### color_mix
|
|
38
|
+
|
|
39
|
+
Mix, blend, lighten, darken, saturate, or desaturate colors.
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{ "operation": "mix", "colors": ["#ff0000", "#0000ff"], "weights": [2, 1] }
|
|
43
|
+
{ "operation": "blend", "colors": ["#ff0000", "#0000ff"], "ratio": 0.3 }
|
|
44
|
+
{ "operation": "lighten", "colors": ["#ff6b35"], "amount": 20 }
|
|
45
|
+
{ "operation": "darken", "colors": ["#ff6b35"], "amount": 15 }
|
|
46
|
+
{ "operation": "saturate", "colors": ["#888888"], "amount": 30 }
|
|
47
|
+
{ "operation": "desaturate", "colors": ["#ff6b35"], "amount": 25 }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### css_gradient
|
|
51
|
+
|
|
52
|
+
Generate CSS gradient code with direction, stops, and browser prefixes.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"type": "linear",
|
|
57
|
+
"colors": ["#ff6b35", "#ffd700", "#00bcd4"],
|
|
58
|
+
"direction": "135deg"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"type": "radial",
|
|
65
|
+
"colors": [
|
|
66
|
+
{ "color": "#ff6b35", "position": 0 },
|
|
67
|
+
{ "color": "#ffd700", "position": 50 },
|
|
68
|
+
{ "color": "#00bcd4", "position": 100 }
|
|
69
|
+
],
|
|
70
|
+
"direction": "circle at center"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Setup
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install
|
|
78
|
+
npm run build
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Claude Desktop Configuration
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"color-tools": {
|
|
87
|
+
"command": "node",
|
|
88
|
+
"args": ["path/to/mcp-color-tools/dist/index.js"]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const color_converter_js_1 = require("./tools/color-converter.js");
|
|
8
|
+
const palette_generator_js_1 = require("./tools/palette-generator.js");
|
|
9
|
+
const contrast_checker_js_1 = require("./tools/contrast-checker.js");
|
|
10
|
+
const color_mixer_js_1 = require("./tools/color-mixer.js");
|
|
11
|
+
const css_gradient_js_1 = require("./tools/css-gradient.js");
|
|
12
|
+
const server = new index_js_1.Server({
|
|
13
|
+
name: "mcp-color-tools",
|
|
14
|
+
version: "1.0.0",
|
|
15
|
+
}, {
|
|
16
|
+
capabilities: {
|
|
17
|
+
tools: {},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
// List available tools
|
|
21
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
22
|
+
tools: [
|
|
23
|
+
{
|
|
24
|
+
name: "color_convert",
|
|
25
|
+
description: "Convert a color between formats: HEX, RGB, HSL, HSV, CMYK. Accepts any format as input and returns all formats.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
color: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Color in any format: HEX (#ff0000), RGB (rgb(255,0,0)), HSL (hsl(0,100%,50%)), HSV (hsv(0,100%,100%)), CMYK (cmyk(0,100,100,0))",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ["color"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "palette_generate",
|
|
39
|
+
description: "Generate a color palette from a base color. Types: complementary, analogous, triadic, split-complementary, monochromatic.",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
color: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Base color in any supported format",
|
|
46
|
+
},
|
|
47
|
+
type: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Palette type: complementary, analogous, triadic, split-complementary, monochromatic",
|
|
50
|
+
enum: [
|
|
51
|
+
"complementary",
|
|
52
|
+
"analogous",
|
|
53
|
+
"triadic",
|
|
54
|
+
"split-complementary",
|
|
55
|
+
"monochromatic",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["color", "type"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "contrast_check",
|
|
64
|
+
description: "Check WCAG contrast ratio between two colors. Reports AA/AAA compliance for normal text, large text, and UI components.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
color1: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "First color (foreground) in any supported format",
|
|
71
|
+
},
|
|
72
|
+
color2: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Second color (background) in any supported format",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
required: ["color1", "color2"],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "color_mix",
|
|
82
|
+
description: "Mix, blend, lighten, darken, saturate, or desaturate colors. For mix/blend provide 2+ colors; for lighten/darken/saturate/desaturate provide 1 color and an amount.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
operation: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "Operation: mix, blend, lighten, darken, saturate, desaturate",
|
|
89
|
+
enum: [
|
|
90
|
+
"mix",
|
|
91
|
+
"blend",
|
|
92
|
+
"lighten",
|
|
93
|
+
"darken",
|
|
94
|
+
"saturate",
|
|
95
|
+
"desaturate",
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
colors: {
|
|
99
|
+
type: "array",
|
|
100
|
+
items: { type: "string" },
|
|
101
|
+
description: "Array of colors in any supported format",
|
|
102
|
+
},
|
|
103
|
+
weights: {
|
|
104
|
+
type: "array",
|
|
105
|
+
items: { type: "number" },
|
|
106
|
+
description: "Optional weights for mixing (same length as colors). Default: equal weights.",
|
|
107
|
+
},
|
|
108
|
+
ratio: {
|
|
109
|
+
type: "number",
|
|
110
|
+
description: "Blend ratio from 0 (first color) to 1 (second color). Default: 0.5.",
|
|
111
|
+
},
|
|
112
|
+
amount: {
|
|
113
|
+
type: "number",
|
|
114
|
+
description: "Amount for lighten/darken/saturate/desaturate (0-100). Default: 10.",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
required: ["operation", "colors"],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "css_gradient",
|
|
122
|
+
description: "Generate CSS gradient code. Supports linear, radial, and conic gradients with direction, stops, and browser prefixes.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
type: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description: "Gradient type: linear, radial, conic",
|
|
129
|
+
enum: ["linear", "radial", "conic"],
|
|
130
|
+
},
|
|
131
|
+
colors: {
|
|
132
|
+
type: "array",
|
|
133
|
+
items: {
|
|
134
|
+
oneOf: [
|
|
135
|
+
{ type: "string" },
|
|
136
|
+
{
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
color: { type: "string" },
|
|
140
|
+
position: {
|
|
141
|
+
type: "number",
|
|
142
|
+
description: "Stop position as percentage (0-100)",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ["color"],
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
description: "Array of colors (strings) or color stops ({color, position}). Minimum 2.",
|
|
150
|
+
},
|
|
151
|
+
direction: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "Direction/shape. Linear: 'to right', '45deg'. Radial: 'circle at center', 'ellipse at top'. Conic: 'from 0deg at center'.",
|
|
154
|
+
},
|
|
155
|
+
includePrefix: {
|
|
156
|
+
type: "boolean",
|
|
157
|
+
description: "Include -webkit- browser prefix. Default: true.",
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
required: ["type", "colors"],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
}));
|
|
165
|
+
// Handle tool calls
|
|
166
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
167
|
+
const { name, arguments: args } = request.params;
|
|
168
|
+
try {
|
|
169
|
+
switch (name) {
|
|
170
|
+
case "color_convert": {
|
|
171
|
+
const result = (0, color_converter_js_1.convertColor)(args?.color);
|
|
172
|
+
return {
|
|
173
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
case "palette_generate": {
|
|
177
|
+
const result = (0, palette_generator_js_1.generatePalette)(args?.color, args?.type);
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
case "contrast_check": {
|
|
183
|
+
const result = (0, contrast_checker_js_1.checkContrast)(args?.color1, args?.color2);
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
case "color_mix": {
|
|
189
|
+
const result = (0, color_mixer_js_1.colorMix)({
|
|
190
|
+
operation: args?.operation,
|
|
191
|
+
colors: args?.colors,
|
|
192
|
+
weights: args?.weights,
|
|
193
|
+
ratio: args?.ratio,
|
|
194
|
+
amount: args?.amount,
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
case "css_gradient": {
|
|
201
|
+
const result = (0, css_gradient_js_1.generateGradient)({
|
|
202
|
+
type: args?.type,
|
|
203
|
+
colors: args?.colors,
|
|
204
|
+
direction: args?.direction,
|
|
205
|
+
includePrefix: args?.includePrefix,
|
|
206
|
+
});
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
default:
|
|
212
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
217
|
+
return {
|
|
218
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
// Start server
|
|
224
|
+
async function main() {
|
|
225
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
226
|
+
await server.connect(transport);
|
|
227
|
+
console.error("MCP Color Tools server running on stdio");
|
|
228
|
+
}
|
|
229
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface RGB {
|
|
2
|
+
r: number;
|
|
3
|
+
g: number;
|
|
4
|
+
b: number;
|
|
5
|
+
}
|
|
6
|
+
export interface HSL {
|
|
7
|
+
h: number;
|
|
8
|
+
s: number;
|
|
9
|
+
l: number;
|
|
10
|
+
}
|
|
11
|
+
export interface HSV {
|
|
12
|
+
h: number;
|
|
13
|
+
s: number;
|
|
14
|
+
v: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CMYK {
|
|
17
|
+
c: number;
|
|
18
|
+
m: number;
|
|
19
|
+
y: number;
|
|
20
|
+
k: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function rgbToHex(rgb: RGB): string;
|
|
23
|
+
export declare function rgbToHsl(rgb: RGB): HSL;
|
|
24
|
+
export declare function hslToRgb(hsl: HSL): RGB;
|
|
25
|
+
export declare function rgbToHsv(rgb: RGB): HSV;
|
|
26
|
+
export declare function hsvToRgb(hsv: HSV): RGB;
|
|
27
|
+
export declare function rgbToCmyk(rgb: RGB): CMYK;
|
|
28
|
+
export declare function cmykToRgb(cmyk: CMYK): RGB;
|
|
29
|
+
export declare function parseColor(input: string): RGB;
|
|
30
|
+
export interface ConvertResult {
|
|
31
|
+
hex: string;
|
|
32
|
+
rgb: {
|
|
33
|
+
r: number;
|
|
34
|
+
g: number;
|
|
35
|
+
b: number;
|
|
36
|
+
css: string;
|
|
37
|
+
};
|
|
38
|
+
hsl: {
|
|
39
|
+
h: number;
|
|
40
|
+
s: number;
|
|
41
|
+
l: number;
|
|
42
|
+
css: string;
|
|
43
|
+
};
|
|
44
|
+
hsv: {
|
|
45
|
+
h: number;
|
|
46
|
+
s: number;
|
|
47
|
+
v: number;
|
|
48
|
+
};
|
|
49
|
+
cmyk: {
|
|
50
|
+
c: number;
|
|
51
|
+
m: number;
|
|
52
|
+
y: number;
|
|
53
|
+
k: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export declare function convertColor(input: string): ConvertResult;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Color Converter — convert between HEX, RGB, HSL, HSV, and CMYK formats.
|
|
3
|
+
// All color math is implemented manually without external libraries.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.rgbToHex = rgbToHex;
|
|
6
|
+
exports.rgbToHsl = rgbToHsl;
|
|
7
|
+
exports.hslToRgb = hslToRgb;
|
|
8
|
+
exports.rgbToHsv = rgbToHsv;
|
|
9
|
+
exports.hsvToRgb = hsvToRgb;
|
|
10
|
+
exports.rgbToCmyk = rgbToCmyk;
|
|
11
|
+
exports.cmykToRgb = cmykToRgb;
|
|
12
|
+
exports.parseColor = parseColor;
|
|
13
|
+
exports.convertColor = convertColor;
|
|
14
|
+
// --- Parsing ---
|
|
15
|
+
function parseHex(hex) {
|
|
16
|
+
let h = hex.replace(/^#/, "").trim();
|
|
17
|
+
if (h.length === 3) {
|
|
18
|
+
h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
19
|
+
}
|
|
20
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) {
|
|
21
|
+
throw new Error(`Invalid HEX color: ${hex}`);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
r: parseInt(h.substring(0, 2), 16),
|
|
25
|
+
g: parseInt(h.substring(2, 4), 16),
|
|
26
|
+
b: parseInt(h.substring(4, 6), 16),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function parseRgbString(str) {
|
|
30
|
+
const match = str.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+\s*)?\)/i);
|
|
31
|
+
if (!match)
|
|
32
|
+
throw new Error(`Invalid RGB string: ${str}`);
|
|
33
|
+
return {
|
|
34
|
+
r: clamp(parseInt(match[1]), 0, 255),
|
|
35
|
+
g: clamp(parseInt(match[2]), 0, 255),
|
|
36
|
+
b: clamp(parseInt(match[3]), 0, 255),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseHslString(str) {
|
|
40
|
+
const match = str.match(/hsla?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)%?\s*,\s*([\d.]+)%?\s*(?:,\s*[\d.]+\s*)?\)/i);
|
|
41
|
+
if (!match)
|
|
42
|
+
throw new Error(`Invalid HSL string: ${str}`);
|
|
43
|
+
return {
|
|
44
|
+
h: parseFloat(match[1]) % 360,
|
|
45
|
+
s: clamp(parseFloat(match[2]), 0, 100),
|
|
46
|
+
l: clamp(parseFloat(match[3]), 0, 100),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseHsvString(str) {
|
|
50
|
+
const match = str.match(/hsv\s*\(\s*([\d.]+)\s*,\s*([\d.]+)%?\s*,\s*([\d.]+)%?\s*\)/i);
|
|
51
|
+
if (!match)
|
|
52
|
+
throw new Error(`Invalid HSV string: ${str}`);
|
|
53
|
+
return {
|
|
54
|
+
h: parseFloat(match[1]) % 360,
|
|
55
|
+
s: clamp(parseFloat(match[2]), 0, 100),
|
|
56
|
+
v: clamp(parseFloat(match[3]), 0, 100),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function parseCmykString(str) {
|
|
60
|
+
const match = str.match(/cmyk\s*\(\s*([\d.]+)%?\s*,\s*([\d.]+)%?\s*,\s*([\d.]+)%?\s*,\s*([\d.]+)%?\s*\)/i);
|
|
61
|
+
if (!match)
|
|
62
|
+
throw new Error(`Invalid CMYK string: ${str}`);
|
|
63
|
+
return {
|
|
64
|
+
c: clamp(parseFloat(match[1]), 0, 100),
|
|
65
|
+
m: clamp(parseFloat(match[2]), 0, 100),
|
|
66
|
+
y: clamp(parseFloat(match[3]), 0, 100),
|
|
67
|
+
k: clamp(parseFloat(match[4]), 0, 100),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function clamp(val, min, max) {
|
|
71
|
+
return Math.max(min, Math.min(max, val));
|
|
72
|
+
}
|
|
73
|
+
// --- Conversion core ---
|
|
74
|
+
function rgbToHex(rgb) {
|
|
75
|
+
const toHex = (n) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0");
|
|
76
|
+
return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
|
|
77
|
+
}
|
|
78
|
+
function rgbToHsl(rgb) {
|
|
79
|
+
const r = rgb.r / 255;
|
|
80
|
+
const g = rgb.g / 255;
|
|
81
|
+
const b = rgb.b / 255;
|
|
82
|
+
const max = Math.max(r, g, b);
|
|
83
|
+
const min = Math.min(r, g, b);
|
|
84
|
+
const delta = max - min;
|
|
85
|
+
let h = 0;
|
|
86
|
+
let s = 0;
|
|
87
|
+
const l = (max + min) / 2;
|
|
88
|
+
if (delta !== 0) {
|
|
89
|
+
s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
|
90
|
+
if (max === r) {
|
|
91
|
+
h = ((g - b) / delta + (g < b ? 6 : 0)) * 60;
|
|
92
|
+
}
|
|
93
|
+
else if (max === g) {
|
|
94
|
+
h = ((b - r) / delta + 2) * 60;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
h = ((r - g) / delta + 4) * 60;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
h: round2(h),
|
|
102
|
+
s: round2(s * 100),
|
|
103
|
+
l: round2(l * 100),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function hslToRgb(hsl) {
|
|
107
|
+
const h = hsl.h;
|
|
108
|
+
const s = hsl.s / 100;
|
|
109
|
+
const l = hsl.l / 100;
|
|
110
|
+
if (s === 0) {
|
|
111
|
+
const v = Math.round(l * 255);
|
|
112
|
+
return { r: v, g: v, b: v };
|
|
113
|
+
}
|
|
114
|
+
const hueToRgb = (p, q, t) => {
|
|
115
|
+
if (t < 0)
|
|
116
|
+
t += 1;
|
|
117
|
+
if (t > 1)
|
|
118
|
+
t -= 1;
|
|
119
|
+
if (t < 1 / 6)
|
|
120
|
+
return p + (q - p) * 6 * t;
|
|
121
|
+
if (t < 1 / 2)
|
|
122
|
+
return q;
|
|
123
|
+
if (t < 2 / 3)
|
|
124
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
125
|
+
return p;
|
|
126
|
+
};
|
|
127
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
128
|
+
const p = 2 * l - q;
|
|
129
|
+
const hNorm = h / 360;
|
|
130
|
+
return {
|
|
131
|
+
r: Math.round(hueToRgb(p, q, hNorm + 1 / 3) * 255),
|
|
132
|
+
g: Math.round(hueToRgb(p, q, hNorm) * 255),
|
|
133
|
+
b: Math.round(hueToRgb(p, q, hNorm - 1 / 3) * 255),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function rgbToHsv(rgb) {
|
|
137
|
+
const r = rgb.r / 255;
|
|
138
|
+
const g = rgb.g / 255;
|
|
139
|
+
const b = rgb.b / 255;
|
|
140
|
+
const max = Math.max(r, g, b);
|
|
141
|
+
const min = Math.min(r, g, b);
|
|
142
|
+
const delta = max - min;
|
|
143
|
+
let h = 0;
|
|
144
|
+
const s = max === 0 ? 0 : delta / max;
|
|
145
|
+
const v = max;
|
|
146
|
+
if (delta !== 0) {
|
|
147
|
+
if (max === r) {
|
|
148
|
+
h = ((g - b) / delta + (g < b ? 6 : 0)) * 60;
|
|
149
|
+
}
|
|
150
|
+
else if (max === g) {
|
|
151
|
+
h = ((b - r) / delta + 2) * 60;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
h = ((r - g) / delta + 4) * 60;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
h: round2(h),
|
|
159
|
+
s: round2(s * 100),
|
|
160
|
+
v: round2(v * 100),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function hsvToRgb(hsv) {
|
|
164
|
+
const h = hsv.h;
|
|
165
|
+
const s = hsv.s / 100;
|
|
166
|
+
const v = hsv.v / 100;
|
|
167
|
+
const c = v * s;
|
|
168
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
169
|
+
const m = v - c;
|
|
170
|
+
let r1 = 0, g1 = 0, b1 = 0;
|
|
171
|
+
if (h < 60) {
|
|
172
|
+
r1 = c;
|
|
173
|
+
g1 = x;
|
|
174
|
+
b1 = 0;
|
|
175
|
+
}
|
|
176
|
+
else if (h < 120) {
|
|
177
|
+
r1 = x;
|
|
178
|
+
g1 = c;
|
|
179
|
+
b1 = 0;
|
|
180
|
+
}
|
|
181
|
+
else if (h < 180) {
|
|
182
|
+
r1 = 0;
|
|
183
|
+
g1 = c;
|
|
184
|
+
b1 = x;
|
|
185
|
+
}
|
|
186
|
+
else if (h < 240) {
|
|
187
|
+
r1 = 0;
|
|
188
|
+
g1 = x;
|
|
189
|
+
b1 = c;
|
|
190
|
+
}
|
|
191
|
+
else if (h < 300) {
|
|
192
|
+
r1 = x;
|
|
193
|
+
g1 = 0;
|
|
194
|
+
b1 = c;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
r1 = c;
|
|
198
|
+
g1 = 0;
|
|
199
|
+
b1 = x;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
r: Math.round((r1 + m) * 255),
|
|
203
|
+
g: Math.round((g1 + m) * 255),
|
|
204
|
+
b: Math.round((b1 + m) * 255),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function rgbToCmyk(rgb) {
|
|
208
|
+
const r = rgb.r / 255;
|
|
209
|
+
const g = rgb.g / 255;
|
|
210
|
+
const b = rgb.b / 255;
|
|
211
|
+
const k = 1 - Math.max(r, g, b);
|
|
212
|
+
if (k === 1) {
|
|
213
|
+
return { c: 0, m: 0, y: 0, k: 100 };
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
c: round2(((1 - r - k) / (1 - k)) * 100),
|
|
217
|
+
m: round2(((1 - g - k) / (1 - k)) * 100),
|
|
218
|
+
y: round2(((1 - b - k) / (1 - k)) * 100),
|
|
219
|
+
k: round2(k * 100),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function cmykToRgb(cmyk) {
|
|
223
|
+
const c = cmyk.c / 100;
|
|
224
|
+
const m = cmyk.m / 100;
|
|
225
|
+
const y = cmyk.y / 100;
|
|
226
|
+
const k = cmyk.k / 100;
|
|
227
|
+
return {
|
|
228
|
+
r: Math.round(255 * (1 - c) * (1 - k)),
|
|
229
|
+
g: Math.round(255 * (1 - m) * (1 - k)),
|
|
230
|
+
b: Math.round(255 * (1 - y) * (1 - k)),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function round2(n) {
|
|
234
|
+
return Math.round(n * 100) / 100;
|
|
235
|
+
}
|
|
236
|
+
// --- Auto-detect and parse any color format ---
|
|
237
|
+
function parseColor(input) {
|
|
238
|
+
const trimmed = input.trim();
|
|
239
|
+
if (/^#?[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(trimmed)) {
|
|
240
|
+
return parseHex(trimmed);
|
|
241
|
+
}
|
|
242
|
+
if (/^rgba?\s*\(/i.test(trimmed)) {
|
|
243
|
+
return parseRgbString(trimmed);
|
|
244
|
+
}
|
|
245
|
+
if (/^hsla?\s*\(/i.test(trimmed)) {
|
|
246
|
+
return hslToRgb(parseHslString(trimmed));
|
|
247
|
+
}
|
|
248
|
+
if (/^hsv\s*\(/i.test(trimmed)) {
|
|
249
|
+
return hsvToRgb(parseHsvString(trimmed));
|
|
250
|
+
}
|
|
251
|
+
if (/^cmyk\s*\(/i.test(trimmed)) {
|
|
252
|
+
return cmykToRgb(parseCmykString(trimmed));
|
|
253
|
+
}
|
|
254
|
+
throw new Error(`Cannot parse color: "${input}". Supported formats: HEX (#ff0000), RGB (rgb(255,0,0)), HSL (hsl(0,100%,50%)), HSV (hsv(0,100%,100%)), CMYK (cmyk(0,100,100,0))`);
|
|
255
|
+
}
|
|
256
|
+
function convertColor(input) {
|
|
257
|
+
const rgb = parseColor(input);
|
|
258
|
+
const hsl = rgbToHsl(rgb);
|
|
259
|
+
const hsv = rgbToHsv(rgb);
|
|
260
|
+
const cmyk = rgbToCmyk(rgb);
|
|
261
|
+
const hex = rgbToHex(rgb);
|
|
262
|
+
return {
|
|
263
|
+
hex,
|
|
264
|
+
rgb: { ...rgb, css: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})` },
|
|
265
|
+
hsl: { ...hsl, css: `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)` },
|
|
266
|
+
hsv,
|
|
267
|
+
cmyk,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type RGB, type HSL } from "./color-converter.js";
|
|
2
|
+
interface MixedColor {
|
|
3
|
+
hex: string;
|
|
4
|
+
rgb: RGB;
|
|
5
|
+
hsl: HSL;
|
|
6
|
+
}
|
|
7
|
+
export declare function mixColors(colors: string[], weights?: number[]): MixedColor;
|
|
8
|
+
export declare function blendColors(color1: string, color2: string, ratio?: number): MixedColor;
|
|
9
|
+
export declare function lightenColor(color: string, amount?: number): MixedColor;
|
|
10
|
+
export declare function darkenColor(color: string, amount?: number): MixedColor;
|
|
11
|
+
export declare function saturateColor(color: string, amount?: number): MixedColor;
|
|
12
|
+
export declare function desaturateColor(color: string, amount?: number): MixedColor;
|
|
13
|
+
export type MixOperation = "mix" | "blend" | "lighten" | "darken" | "saturate" | "desaturate";
|
|
14
|
+
export interface MixRequest {
|
|
15
|
+
operation: MixOperation;
|
|
16
|
+
colors: string[];
|
|
17
|
+
weights?: number[];
|
|
18
|
+
ratio?: number;
|
|
19
|
+
amount?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function colorMix(req: MixRequest): MixedColor;
|
|
22
|
+
export {};
|