diff --git a/lib/index.js b/lib/index.js index a248061..85938be 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,11 +6,14 @@ var palette = require('./palette'); var color = module.exports = {}; -color.diff = diff.ciede2000; -color.rgb_to_lab = convert.rgb_to_lab; -color.rgba_to_lab = convert.rgba_to_lab; -color.map_palette = palette.map_palette; -color.palette_map_key = palette.palette_map_key; +color.diff = diff.ciede2000; +color.rgb_to_lab = convert.rgb_to_lab; +color.rgba_to_lab = convert.rgba_to_lab; +color.map_palette = palette.map_palette; +color.palette_map_key = palette.palette_map_key; +color.map_palette_lab = palette.map_palette_lab; +color.lab_palette_map_key = palette.lab_palette_map_key; +color.match_palette_lab = palette.match_palette_lab; color.closest = function(target, relative, bc) { var key = color.palette_map_key(target); @@ -27,3 +30,11 @@ color.furthest = function(target, relative, bc) { return result[key]; }; + +color.closest_lab = function(target, relative) { + return color.match_palette_lab(target, relative, false); +}; + +color.furthest_lab = function(target, relative) { + return color.match_palette_lab(target, relative, true); +}; diff --git a/lib/palette.js b/lib/palette.js index 80983ae..38441e4 100644 --- a/lib/palette.js +++ b/lib/palette.js @@ -29,8 +29,11 @@ /** * EXPORTS */ -exports.map_palette = map_palette; -exports.palette_map_key = palette_map_key; +exports.map_palette = map_palette; +exports.map_palette_lab = map_palette_lab; +exports.match_palette_lab = match_palette_lab; +exports.palette_map_key = palette_map_key; +exports.lab_palette_map_key = lab_palette_map_key; /** * IMPORTS @@ -56,6 +59,16 @@ function palette_map_key(c) return s; } +/** +* Returns the hash key used for a {labcolor} in a {labpalettemap} +* @param {labcolor} c should have fields L,a,b +* @return {string} +*/ +function lab_palette_map_key(c) +{ + return "L" + c.L + "a" + c.a + "b" + c.b; +} + /** * Returns a mapping from each color in a to the closest/farthest color in b * @param [{rgbcolor}] a each element should have fields R,G,B @@ -97,6 +110,54 @@ function map_palette(a, b, type, bc) return c; } +/** +* Returns the closest (or furthest) color to target_color in palette, operating in the L,a,b colorspace for performance +* @param {labcolor} target_color should have fields L,a,b +* @param [{labcolor}] palette each element should have fields L,a,b +* @param 'find_furthest' should be falsy to find the closest color +* @return {labcolor} +*/ +function match_palette_lab(target_color, palette, find_furthest) +{ + var color2, current_color_diff; + var best_color = palette[0]; + var best_color_diff = ciede2000(target_color, best_color); + for (var idx2 = 1, l = palette.length; idx2 < l; idx2 += 1) + { + color2 = palette[idx2]; + current_color_diff = ciede2000(target_color, color2); + + if( + (!find_furthest && (current_color_diff < best_color_diff)) || + (find_furthest && (current_color_diff > best_color_diff)) + ) + { + best_color = color2; + best_color_diff = current_color_diff; + } + } + return best_color; +} + +/** +* Returns a mapping from each color in a to the closest color in b +* @param [{labcolor}] a each element should have fields L,a,b +* @param [{labcolor}] b each element should have fields L,a,b +* @param 'type' should be the string 'closest' or 'furthest' +* @return {labpalettemap} +*/ +function map_palette_lab(a, b, type) +{ + var c = {}; + var find_furthest = type === 'furthest'; + for (var idx1 = 0; idx1 < a.length; idx1 += 1) + { + var color1 = a[idx1]; + c[lab_palette_map_key(color1)] = match_palette_lab(color1, b, find_furthest); + } + return c; +} + /** * INTERNAL FUNCTIONS */ diff --git a/test/palette.js b/test/palette.js index 18df5af..588dbe8 100644 --- a/test/palette.js +++ b/test/palette.js @@ -31,6 +31,7 @@ */ var assert = require('assert'); var color_palette = require('../lib/palette'); +var color_convert = require('../lib/convert'); /** * CONSTANTS @@ -42,9 +43,20 @@ var navy = {'R':0 , 'G':0 ,'B':128}; var blue = {'R':0 , 'G':0 ,'B':255}; var yellow = {'R':255 , 'G':255 ,'B':0}; var gold = {'R':255 , 'G':215 ,'B':0}; + +var white_lab = color_convert.rgb_to_lab(white); +var black_lab = color_convert.rgb_to_lab(black); +var navy_lab = color_convert.rgb_to_lab(navy); +var blue_lab = color_convert.rgb_to_lab(blue); +var yellow_lab = color_convert.rgb_to_lab(yellow); +var gold_lab = color_convert.rgb_to_lab(gold); + var colors1 = [white, black, navy, blue, yellow, gold] +var colors1_lab = [white_lab, black_lab, navy_lab, blue_lab, yellow_lab, gold_lab] var colors2 = [white, black, blue, gold] +var colors2_lab = [white_lab, black_lab, blue_lab, gold_lab] var colors3 = [white, black, yellow, blue] +var colors3_lab = [white_lab, black_lab, yellow_lab, blue_lab] var white_a = {'R':255 , 'G':255 ,'B':255, 'A': 1.0}; var black_a = {'R':0 , 'G':0 ,'B':0, 'A': 1.0}; @@ -54,6 +66,14 @@ var yellow_a = {'R':255 , 'G':255 ,'B':0, 'A': 1.0}; var gold_a = {'R':255 , 'G':215 ,'B':0, 'A': 1.0}; var colors1_a = [white_a, black_a, navy_a, blue_a, yellow_a, gold_a] +var white_a_lab = color_convert.rgb_to_lab(white_a); +var black_a_lab = color_convert.rgb_to_lab(black_a); +var navy_a_lab = color_convert.rgb_to_lab(navy_a); +var blue_a_lab = color_convert.rgb_to_lab(blue_a); +var yellow_a_lab = color_convert.rgb_to_lab(yellow_a); +var gold_a_lab = color_convert.rgb_to_lab(gold_a); +var colors1_a_lab = [white_a_lab, black_a_lab, navy_a_lab, blue_a_lab, yellow_a_lab, gold_a_lab] + /** * TESTS */ @@ -112,6 +132,89 @@ describe('palette', function(){ }); }) + + describe('#map_palette_lab()', function (){ + it('should map all colors to themselves when possible #1', + function(){ + var expected1_1 = {}; + expected1_1[color_palette.lab_palette_map_key(white_lab)] = white_lab; + expected1_1[color_palette.lab_palette_map_key(black_lab)] = black_lab; + expected1_1[color_palette.lab_palette_map_key(navy_lab)] = navy_lab; + expected1_1[color_palette.lab_palette_map_key(blue_lab)] = blue_lab; + expected1_1[color_palette.lab_palette_map_key(yellow_lab)] = yellow_lab; + expected1_1[color_palette.lab_palette_map_key(gold_lab)] = gold_lab; + assert.deepEqual(expected1_1, + color_palette.map_palette_lab(colors1_lab, colors1_lab)); + }); + it('should map all colors to themselves when possible #2', + function(){ + var expected1_2 = {}; + expected1_2[color_palette.lab_palette_map_key(white_a_lab)] = white_a_lab; + expected1_2[color_palette.lab_palette_map_key(black_a_lab)] = black_a_lab; + expected1_2[color_palette.lab_palette_map_key(navy_a_lab)] = navy_a_lab; + expected1_2[color_palette.lab_palette_map_key(blue_a_lab)] = blue_a_lab; + expected1_2[color_palette.lab_palette_map_key(yellow_a_lab)] = yellow_a_lab; + expected1_2[color_palette.lab_palette_map_key(gold_a_lab)] = gold_a_lab; + assert.deepEqual(expected1_2, + color_palette.map_palette_lab(colors1_a_lab, colors1_a_lab)); + }); + it('should map navy->blue and yellow->gold when navy and yellow are missing', + function(){ + var expected2 = {}; + expected2[color_palette.lab_palette_map_key(white_lab)] = white_lab; + expected2[color_palette.lab_palette_map_key(black_lab)] = black_lab; + expected2[color_palette.lab_palette_map_key(navy_lab)] = blue_lab; + expected2[color_palette.lab_palette_map_key(blue_lab)] = blue_lab; + expected2[color_palette.lab_palette_map_key(yellow_lab)] = gold_lab; + expected2[color_palette.lab_palette_map_key(gold_lab)] = gold_lab; + assert.deepEqual(expected2, + color_palette.map_palette_lab(colors1_lab, colors2_lab)); + }); + it('should map white->black & black,navy,blue->yellow & yellow,gold->blue', + function(){ + var expected3 = {}; + expected3[color_palette.lab_palette_map_key(white_lab)] = black_lab; + expected3[color_palette.lab_palette_map_key(black_lab)] = yellow_lab; + expected3[color_palette.lab_palette_map_key(navy_lab)] = yellow_lab; + expected3[color_palette.lab_palette_map_key(blue_lab)] = yellow_lab; + expected3[color_palette.lab_palette_map_key(yellow_lab)] = blue_lab; + expected3[color_palette.lab_palette_map_key(gold_lab)] = blue_lab; + assert.deepEqual(expected3, + color_palette.map_palette_lab(colors1_lab, + colors3_lab, + 'furthest')); + }); + + }) + + describe('#match_palette_lab()', function (){ + it('should match map_palette results for closest', + function() { + assert.deepEqual(color_palette.match_palette_lab(white_lab, colors1_lab), white_lab); + assert.deepEqual(color_palette.match_palette_lab(black_lab, colors1_lab), black_lab); + assert.deepEqual(color_palette.match_palette_lab(navy_lab, colors1_lab), navy_lab); + assert.deepEqual(color_palette.match_palette_lab(blue_lab, colors1_lab), blue_lab); + assert.deepEqual(color_palette.match_palette_lab(yellow_lab, colors1_lab), yellow_lab); + assert.deepEqual(color_palette.match_palette_lab(gold_lab, colors1_lab), gold_lab); + + assert.deepEqual(color_palette.match_palette_lab(white_lab, colors2_lab), white_lab); + assert.deepEqual(color_palette.match_palette_lab(black_lab, colors2_lab), black_lab); + assert.deepEqual(color_palette.match_palette_lab(navy_lab, colors2_lab), blue_lab); + assert.deepEqual(color_palette.match_palette_lab(blue_lab, colors2_lab), blue_lab); + assert.deepEqual(color_palette.match_palette_lab(yellow_lab, colors2_lab), gold_lab); + assert.deepEqual(color_palette.match_palette_lab(gold_lab, colors2_lab), gold_lab); + }); + + it('should match map_palette results for furthest', + function() { + assert.deepEqual(color_palette.match_palette_lab(white_lab, colors3_lab, true), black_lab); + assert.deepEqual(color_palette.match_palette_lab(black_lab, colors3_lab, true), yellow_lab); + assert.deepEqual(color_palette.match_palette_lab(navy_lab, colors3_lab, true), yellow_lab); + assert.deepEqual(color_palette.match_palette_lab(blue_lab, colors3_lab, true), yellow_lab); + assert.deepEqual(color_palette.match_palette_lab(yellow_lab, colors3_lab, true), blue_lab); + assert.deepEqual(color_palette.match_palette_lab(gold_lab, colors3_lab, true), blue_lab); + }); + }) }); // Local Variables: