mix_colors
Combine multiple colors using specified ratios and blend modes to create new color mixtures in various color spaces.
Instructions
Mix multiple colors with specified ratios and blend modes. Supports various color spaces and blend modes for different mixing effects.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| colors | Yes | Array of colors to mix (2-10 colors) | |
| ratios | No | Optional mixing ratios for each color (must sum to 1.0) | |
| blend_mode | No | Blend mode for color mixing | normal |
| color_space | No | Color space for mixing calculations | rgb |
Implementation Reference
- src/tools/mix-colors.ts:293-468 (handler)Core handler function that validates input using Joi schema, parses color strings to UnifiedColor objects, calculates mixing ratios, performs color mixing in specified color space (RGB, HSL, LAB, LCH) or applies blend modes sequentially, computes accessibility contrast ratios, generates recommendations, and formats the success/error response.async function mixColorsHandler( params: unknown ): Promise<ToolResponse | ErrorResponse> { const startTime = Date.now(); try { // Validate parameters const { error, value } = mixColorsSchema.validate(params); if (error) { return createErrorResponse( 'mix_colors', 'INVALID_PARAMETERS', `Invalid parameters: ${error.details.map(d => d.message).join(', ')}`, Date.now() - startTime, { details: error.details, } ); } const { colors: colorStrings, ratios, blend_mode, color_space, } = value as MixColorsParams; // Parse colors const colors: UnifiedColor[] = []; for (let i = 0; i < colorStrings.length; i++) { const colorString = colorStrings[i]; if (!colorString) continue; try { colors.push(new UnifiedColor(colorString)); } catch (error) { return createErrorResponse( 'mix_colors', 'INVALID_COLOR', `Invalid color at index ${i}: ${colorStrings[i]}`, Date.now() - startTime, { details: { colorIndex: i, providedColor: colorStrings[i], error: error instanceof Error ? error.message : 'Unknown error', }, suggestions: [ 'Ensure all colors are in valid format (hex, rgb, hsl, etc.)', 'Check color syntax and values', ], } ); } } // Calculate ratios if not provided const finalRatios = ratios || colors.map(() => 1 / colors.length); // Mix colors based on color space let mixedColor: UnifiedColor; if (blend_mode === 'normal') { // Use color space mixing for normal blend mode switch (color_space) { case 'hsl': mixedColor = mixColorsInHSL(colors, finalRatios); break; case 'lab': mixedColor = mixColorsInLAB(colors, finalRatios); break; case 'lch': mixedColor = mixColorsInLCH(colors, finalRatios); break; default: // rgb mixedColor = mixColorsInRGB(colors, finalRatios); } } else { // Apply blend modes sequentially for non-normal modes if (colors.length === 0) { throw new Error('No colors provided for mixing'); } mixedColor = colors[0]!; for (let i = 1; i < colors.length; i++) { const nextColor = colors[i]; if (nextColor && blend_mode) { mixedColor = applyBlendMode(mixedColor, nextColor, blend_mode); } } } const executionTime = Date.now() - startTime; // Generate accessibility notes const accessibilityNotes: string[] = []; const contrastWhite = mixedColor.getContrastRatio('#ffffff'); const contrastBlack = mixedColor.getContrastRatio('#000000'); if (contrastWhite >= 4.5) { accessibilityNotes.push( 'Mixed color has good contrast against white backgrounds' ); } if (contrastBlack >= 4.5) { accessibilityNotes.push( 'Mixed color has good contrast against black backgrounds' ); } if (contrastWhite < 3 && contrastBlack < 3) { accessibilityNotes.push( 'Mixed color may have poor contrast - consider adjusting for accessibility' ); } // Generate recommendations const recommendations: string[] = []; if (blend_mode !== 'normal' && colors.length > 2) { recommendations.push( 'For complex blending with multiple colors, consider mixing pairs sequentially' ); } if (color_space === 'lab' || color_space === 'lch') { recommendations.push( 'LAB/LCH color space provides perceptually uniform mixing' ); } return createSuccessResponse( 'mix_colors', { mixed_color: { hex: mixedColor.hex, rgb: mixedColor.rgb, hsl: mixedColor.hsl, hsv: mixedColor.hsv, }, input_colors: colorStrings, ratios: finalRatios, blend_mode, color_space, mixing_details: { total_colors: colors.length, effective_ratios: finalRatios, color_space_used: color_space, blend_mode_used: blend_mode, }, }, executionTime, { colorSpaceUsed: color_space || 'rgb', accessibilityNotes: accessibilityNotes, recommendations, } ); } catch (error) { const executionTime = Date.now() - startTime; logger.error('Error in mix_colors tool', { error: error as Error }); return createErrorResponse( 'mix_colors', 'PROCESSING_ERROR', 'An error occurred while mixing colors', executionTime, { details: { error: error instanceof Error ? error.message : 'Unknown error', }, suggestions: [ 'Check that all input colors are valid', 'Verify ratios sum to 1.0 if provided', 'Try with fewer colors or simpler blend modes', ], } ); } }
- src/tools/mix-colors.ts:28-71 (schema)Joi validation schema for input parameters, including custom validation for ratios array (matching colors length and summing to 1.0), with detailed error messages.const mixColorsSchema = Joi.object({ colors: Joi.array() .items(Joi.string().required()) .min(2) .max(10) .required() .messages({ 'array.min': 'At least 2 colors are required for mixing', 'array.max': 'Maximum 10 colors can be mixed at once', }), ratios: Joi.array() .items(Joi.number().min(0).max(1)) .optional() .custom((value, helpers) => { const colors = helpers.state.ancestors[0].colors; if (value && value.length !== colors.length) { return helpers.error('array.length'); } const sum = value ? value.reduce((a: number, b: number) => a + b, 0) : 0; if (value && Math.abs(sum - 1) > 0.001) { return helpers.error('array.sum'); } return value; }) .messages({ 'array.length': 'Number of ratios must match number of colors', 'array.sum': 'Ratios must sum to 1.0', }), blend_mode: Joi.string() .valid( 'normal', 'multiply', 'screen', 'overlay', 'color_burn', 'color_dodge', 'darken', 'lighten', 'difference', 'exclusion' ) .default('normal'), color_space: Joi.string().valid('rgb', 'hsl', 'lab', 'lch').default('rgb'), });
- src/tools/mix-colors.ts:475-514 (schema)JSON Schema (OpenAI tool format) defining the input parameters for the MCP tool, used for tool calling validation.type: 'object', properties: { colors: { type: 'array', items: { type: 'string' }, minItems: 2, maxItems: 10, description: 'Array of colors to mix (2-10 colors)', }, ratios: { type: 'array', items: { type: 'number', minimum: 0, maximum: 1 }, description: 'Optional mixing ratios for each color (must sum to 1.0)', }, blend_mode: { type: 'string', enum: [ 'normal', 'multiply', 'screen', 'overlay', 'color_burn', 'color_dodge', 'darken', 'lighten', 'difference', 'exclusion', ], default: 'normal', description: 'Blend mode for color mixing', }, color_space: { type: 'string', enum: ['rgb', 'hsl', 'lab', 'lch'], default: 'rgb', description: 'Color space for mixing calculations', }, }, required: ['colors'], },
- src/tools/index.ts:134-134 (registration)Registers the mixColorsTool instance with the central ToolRegistry singleton.toolRegistry.registerTool(mixColorsTool);
- src/tools/mix-colors.ts:73-100 (helper)Helper function implementing weighted average color mixing in RGB color space (default space). Similar helpers exist for HSL, LAB, LCH.function mixColorsInRGB( colors: UnifiedColor[], ratios: number[] ): UnifiedColor { let r = 0, g = 0, b = 0, a = 0; for (let i = 0; i < colors.length; i++) { const color = colors[i]; const ratio = ratios[i]; if (!color || ratio === undefined) continue; const rgb = color.rgb; r += rgb.r * ratio; g += rgb.g * ratio; b += rgb.b * ratio; a += (rgb.a || 1) * ratio; } return UnifiedColor.fromRgb( Math.round(Math.max(0, Math.min(255, r))), Math.round(Math.max(0, Math.min(255, g))), Math.round(Math.max(0, Math.min(255, b))), Math.max(0, Math.min(1, a)) ); }