Copy link to clipboard
Copied
I’ve seen tutorials on how to add the Foreground Color Picker to the keyboard shortcuts available in Illustrator, but when I open the Keyboard Shortcuts (Mac using Command-option-shift-K) and look under tools, there is no option to assign a key to either the Foreground or Background Color Picker. It seems they used to be in the Keyboard Shortcut menu (around 2016 and 2017), but I’m not seeing them now. Is there another way to access those, other than double clicking on the squares in the tools panel? Or are they possibly called something else now, so I’m not seeing them?
Thanks in advance,
Tom Carlson
1 Correct answer
You can prompt the color picker to open via scripting:
app.showColorPicker(
app.activeDocument[
"default" + (app.isFillActive() ? "Fill" : "Stroke") + "Color"
]
);
If you were to save the above as a text file with a name like "openColorPicker.jsx", you can place it in the Program Files for your startup scripts and record an Action with Insert Menu Item (searching for "openColorPicker") targeting this script which lets you trigger it via hotkeys assigned to the action like F12. Th
...Explore related tutorials & articles
Copy link to clipboard
Copied
There is no foreground color in Illustrator, there is a Fill and Stroke color option.
Use X to switch between targeting Fill or Stroke color.
Use Shift X to switch the Fill and Stroke colors.
Copy link to clipboard
Copied
I’ve seen tutorials on how to add the Foreground Color Picker to the keyboard shortcuts available in Illustrator
By TomMxEdit
Are you sure these weren't for Photoshop? Can you link to one?
Copy link to clipboard
Copied
Thanks so much Doug and Ton. You are both correct and I will now wipe the egg off my face: That was a Photoshop tutorial I was watching -- I had done a search with "Illustrator" but what came up was a PS tutorial and I wasn’t paying close enough attention.
So, I would assume I can’t add the Color Picker as a keyboard shortcut then, is that correct? The reason I’d like to do this is to make the Color Picker more easily accessible to a keyboard macro program (Keyboard Maestro), which doesn’t seem to want to open it via a double-click. I’ll try playing with some of the parameters in KM and also try to open it as an action via Illustrator.
Thanks again for your help!
Tom
Copy link to clipboard
Copied
You can prompt the color picker to open via scripting:
app.showColorPicker(
app.activeDocument[
"default" + (app.isFillActive() ? "Fill" : "Stroke") + "Color"
]
);
If you were to save the above as a text file with a name like "openColorPicker.jsx", you can place it in the Program Files for your startup scripts and record an Action with Insert Menu Item (searching for "openColorPicker") targeting this script which lets you trigger it via hotkeys assigned to the action like F12. Then I'd use macros from AutoHotKey, Keyboard Maestro, Sharpkeys or etc to remap some other key combination to send F12 and trigger the action.
Copy link to clipboard
Copied
I have one more question: Although the Color Picker window opens correctly using the script and I can type numbers in the hex input -- or for that matter drag the circle around to select colors or change any of the values by typing -- those changes are not reflected in the selected text I am trying to change. In other words, when I hit "OK" no changes happen to the selected text. On the other hand, if I open the Color Picker by double clicking on the stroke or fill squares in the toolbar, any changes I make are then made to the selected text as soon as I hit "OK."
I’m sure I must just be missing a step or two here, but I don’t know what it might be...
Tom
Copy link to clipboard
Copied
Sorry, didn't realize that opening the picker from scripting doesn't fire a Set Color command like an action would if you were recording. Someone else may have an easier way of iterating through the selection and applying color but try this:
/**
* This isn't as simple of an issue as it may seem. While the below script does work decently well, there are a few caveats:
* - It won't assign color to anything that doesn't already have a color already.
* If this weren't the case, it would paint CompoundPathItems with a fill color and cover their children PathItems.
* - It isn't truly recursive beyond the 2nd depth. Complicated nested compound paths or groups will have deep descendents ignored.
*/
function get(type, parent, deep) {
if (arguments.length == 1 || !parent) {
parent = app.activeDocument;
deep = true;
}
var result = [];
if (!parent[type]) return result;
for (var i = 0; i < parent[type].length; i++) {
result.push(parent[type][i]);
if (parent[type][i][type] && deep)
result = [].concat(result, get(type, parent[type][i], deep));
else if (
/(compound|group)/i.test(parent[type][i].typename) &&
parent[type][i].pathItems
)
result = [].concat(result, get("pathItems", parent[type][i], deep));
}
return result;
}
Array.prototype.filter = function(callback) {
var filtered = [];
for (var i = 0; i < this.length; i++)
if (callback(this[i], i, this)) filtered.push(this[i]);
return filtered;
};
Array.prototype.forEach = function(callback) {
for (var i = 0; i < this.length; i++) callback(this[i], i, this);
};
function openColorPickerAndSetColor() {
var bool = app.isFillActive(),
color;
try {
color = app.showColorPicker(
app.activeDocument["default" + (bool ? "Fill" : "Stroke") + "Color"]
);
} catch (err) {
// Specified value greater error, meaning there's more than one active fill/stroke color.
// How to best handle this? No simple way unless we just create a null color instead:
color = app.showColorPicker(new RGBColor());
}
var setProp, root;
get("selection")
.filter(function(item) {
return /(path|textframe)/i.test(item.typename);
})
.filter(function(item) {
if (/textframe/i.test(item.typename)) {
return !/(nocolor|null)/i.test(
item.textRange.characterAttributes[
(bool ? "fill" : "stroke") + "Color"
] + ""
);
} else return item[(bool ? "fille" : "stroke") + "d"];
})
.forEach(function(item) {
root = /textframe/i.test(item.typename)
? item.textRange.characterAttributes
: item;
setProp = (bool ? "fill" : "stroke") + "Color";
root[setProp] = color;
});
}
openColorPickerAndSetColor();
Copy link to clipboard
Copied
Wow! That certainly is an impressive bit of coding there to open up the Color Picker and have access to it. It seems opeing the Color Picker and preparing it to receive hex code input for a different fill color of text is more complex than it at first appears. I’m thinking this script must have the further functionality for either inputting values for the fill or stroke color of an object, is that correct? Does it test for the current focus of either fill or stroke? Or is there something else going on in the script?
Sorry for the newbie questions, but scripting isn’t something I have much experience with. That said, thanks for the script and for the informative comments as well. It’s an education following along!!
As far as my needs are concerned, this is solved again, Inventsable, and I’ve marked it as such.
Tom
Copy link to clipboard
Copied
It seems opeing the Color Picker and preparing it to receive hex code input for a different fill color of text is more complex than it at first appears.
It is. Scripting doesn't really simulate user actions, so if you were to want to apply a certain color to your selection you'd have to do all the logic above to determine which objects in your selection should be painted and etc. This actually set me down a path of looking further into AIA text (like exporting Actions) because there is a "Set Color" command that Actions use which skips all of this. If you could properly create the AIA text for a Set Color command from some given color, you could skip all the above logic and issues with the script -- it would more closely replicate the same action that a user would be doing.
I’m thinking this script must have the further functionality for either inputting values for the fill or stroke color of an object, is that correct? Does it test for the current focus of either fill or stroke? Or is there something else going on in the script?
That's right. The first line in the main function is:
var bool = app.isFillActive(),
This returns true or false (a boolean, hence the name) and then will apply to either Fill or Stroke depending on which it was opened with. So essentially the script checks if you're reassinging Fill or Stroke before doing anything else then uses that as the basis to know which it should be applying -- this again is a pretty longwinded way of doing it and you could much more easily handle this through Actions but that requires a lot of supporting work be done first (like properly encoding keys noted in that Github link).
Copy link to clipboard
Copied
As a followup, here's an alternative that has none of the issues the original and manual example above did with recursion, complicated nesting, and etc:
function asciiToHex(input) {
var output = "";
for (var i = 0; i < input.toString().length; i++)
output += input.toString().charCodeAt(i).toString(16);
return output;
}
function asciiToDecimal(input) {
return parseInt(asciiToHex(input), 16);
}
function openColorPickerAndSetColor() {
var bool = app.isFillActive(),
actionStr =
"/version 3\r\n/name [ 7\r\n\t74656d70536574\r\n]\r\n/isOpen 1\r\n/actionCount 1\r\n/action-1 {\r\n\t/name [ 10\r\n\t\t74656d70416374696f6e\r\n\t]\r\n\t/keyIndex 0\r\n\t/colorIndex 0\r\n\t/isOpen 0\r\n\t/eventCount 1\r\n\t/event-1 {\r\n\t\t/useRulersIn1stQuadrant 0\r\n\t\t/internalName (ai_plugin_setColor)\r\n\t\t/localizedName [ 9\r\n\t\t\t53657420636f6c6f72\r\n\t\t]\r\n\t\t/isOpen 1\r\n\t\t/isOn 1\r\n\t\t/hasDialog 0\r\n\t\t/parameterCount 6\r\n\t\t/parameter-1 {\r\n\t\t\t/key 1768186740\r\n\t\t\t/showInPalette -1\r\n\t\t\t/type (ustring)\r\n\t\t\t/value [ $TYPEHEXLENGTH$\r\n\t\t\t\t$TYPEHEX$\r\n\t\t\t]\r\n\t\t}\r\n\t\t/parameter-2 {\r\n\t\t\t/key 1718185068\r\n\t\t\t/showInPalette -1\r\n\t\t\t/type (boolean)\r\n\t\t\t/value $ISFILL$\r\n\t\t}\r\n\t\t/parameter-3 {\r\n\t\t\t/key 1954115685\r\n\t\t\t/showInPalette -1\r\n\t\t\t/type (enumerated)\r\n\t\t\t/name [ 9\r\n\t\t\t\t52474220636f6c6f72\r\n\t\t\t]\r\n\t\t\t/value 2\r\n\t\t}\r\n\t\t/parameter-4 {\r\n\t\t\t/key 1919247406\r\n\t\t\t/showInPalette -1\r\n\t\t\t/type (real)\r\n\t\t\t/value $RVALUE$\r\n\t\t}\r\n\t\t/parameter-5 {\r\n\t\t\t/key 1735550318\r\n\t\t\t/showInPalette -1\r\n\t\t\t/type (real)\r\n\t\t\t/value $GVALUE$\r\n\t\t}\r\n\t\t/parameter-6 {\r\n\t\t\t/key 1651275109\r\n\t\t\t/showInPalette -1\r\n\t\t\t/type (real)\r\n\t\t\t/value $BVALUE$\r\n\t\t}\r\n\t}\r\n}",
color;
try {
color = app.showColorPicker(
app.activeDocument["default" + (bool ? "Fill" : "Stroke") + "Color"]
);
} catch (err) {
// Specified value greater error, meaning there's more than one active fill/stroke color.
// How to best handle this? No simple way unless we just create a null color instead:
color = app.showColorPicker(new RGBColor());
}
var typeHex = asciiToHex((bool ? "Fill" : "Stroke") + " color");
var params = [
{ find: "$RVALUE$", replace: Math.floor(color.red) + ".0" },
{ find: "$GVALUE$", replace: Math.floor(color.green) + ".0" },
{ find: "$BVALUE$", replace: Math.floor(color.blue) + ".0" },
{ find: "$ISFILL$", replace: bool ? "1" : "0" },
{ find: "$TYPEHEX$", replace: typeHex },
{ find: "$TYPEHEXLENGTH$", replace: typeHex.length / 2 },
];
for (var i = 0; i < params.length; i++)
actionStr = actionStr.replace(params[i].find, params[i].replace);
runActionFromString("tempAction", "tempSet", actionStr);
}
openColorPickerAndSetColor();
function runActionFromString(actionName, actionSet, contents) {
var tmp = File(Folder.desktop + "/tmp.aia");
tmp.open("w");
tmp.write(contents);
tmp.close();
app.loadAction(tmp);
app.doScript(actionName, actionSet, false);
app.unloadAction(actionSet, "");
tmp.remove();
}
This takes a pre-recorded Set Color action then injects the current content while encoding new data, loads then executes and unloads (removes) the action from the Actions panel. That will only work for RGBColors unless modifying the original string, though you can still place it in your startup scripts to assign a hotkey.
It would obviously be better to parse AIA to JSON then modify the properties and parse from JSON back to AIA then execute it, but if you'll always be using RGBColor this is probably the next best alternative.
Copy link to clipboard
Copied
Thanks again, especially for the Rabbit Hole Journey here:
“This actually set me down a path of looking further into AIA text (like exporting Actions) because there is a "Set Color" command that Actions use which skips all of this.”
That is beautifully laid out, so that even a rank amature (before retirement, I was a film music editor) can sort-of follow it.
A couple of things I noticed were:
1) If run "directly" by selecting the script as a script menu item, the script opens up the Color Picker and allows manual input of a color code number. In my case, I run it from the script menu using Keyboard Maestro as such:
a) Use KM to prep the line of text, which is a title -- i.e set the font, style, size, underlining and kerning
b) Use KM to run your latest script from the script menu -- to open the Color Picker
c) Use KM to enter the color of this title by automatically typing it into the color input window and automatically clicking on "Okay"
d) Use KM to center the text and moves it to the top of the page.
This method works perfectly as long as I leave about .5 seconds between certain steps of the process.
2) If I try to run the script inside of an Illustrator action -- run by pressing F12 -- when I press that hotkey the Color Picker opens with focus on the hex input window, ready for me to type a number. I manually input my selected color number. However, when I hit "Okay," Illustrator freezes (spinning beachball).
So, although the latest script runs properly when selecting it manually as a script menu item or from KM as a script menu item, it freezes Illustrator when called from an Illustrator action. Is that what you would expect? Also, am I supposed to be entering the pre-determined color code someplace before running the script? Or does the script simply "take" you to the point of entering the hex number in the proper Color Picker window (which is what it seems to be doing)?
Thanks again for the very interesting solution to this and for providing the first baby steps in my JSON education. Do you have a recommendation for the best book(s) to learn about JSON? Or would you say that language is on the way out and one’s time would be better spent learning a different language?
Tom
Copy link to clipboard
Copied
1) If run "directly" by selecting the script as a script menu item, the script opens up the Color Picker and allows manual input of a color code number. In my case, I run it from the script menu using Keyboard Maestro as such:
If I were doing this through AutoHotKey (which is very similar to Keyboard Maestro) I'd probably run a sequence of key presses starting with Alt + F (selects File menu), R (selects File > Scripts), then double up keys to run the last entry in the Scripts menu. It's not ideal but it could be an alternative that also works given the problem you note with #2.
2) If I try to run the script inside of an Illustrator action -- run by pressing F12 -- when I press that hotkey the Color Picker opens with focus on the hex input window, ready for me to type a number. I manually input my selected color number. However, when I hit "Okay," Illustrator freezes (spinning beachball).
So, although the latest script runs properly when selecting it manually as a script menu item or from KM as a script menu item, it freezes Illustrator when called from an Illustrator action. Is that what you would expect?
I'd have to look into this. I've seen mentions on other threads about dealing with Actions that running one within another (like we're doing here, using an Action to run a script which itself creates and runs an Action) causes freezing. I wasn't sure if this was OS or app-version specific but it may just be a general rule-of-thumb. If this were the case (and it's very likely) then it does make sense here because the "inside" Action isn't opening the color picker but setting the color from our color value; the moment the picker closes would be the exact time freezing would occur.
Also, am I supposed to be entering the pre-determined color code someplace before running the script? Or does the script simply "take" you to the point of entering the hex number in the proper Color Picker window (which is what it seems to be doing)?
The script is supposed to take you there. Prompting the picker with a color value just sets the value to that given color, so if you were to have a green active fill then the picker should open on that green instead of coming up as black each time and so on.
Thanks again for the very interesting solution to this and for providing the first baby steps in my JSON education. Do you have a recommendation for the best book(s) to learn about JSON? Or would you say that language is on the way out and one’s time would be better spent learning a different language?
Well, JSON isn't formally a part of Extendscript but otherwise it's very much standard practice and arguably more relevant in modern coding than ever before. JSON is Javascript Object Notation, which is (and this is somewhat a simplification) a way of writing a given Javascript value to a string for use in a text file or as something like a message between two endpoints (like how Slack in your browser can prompt or open the desktop app version of Slack through WebSockets). So for instance, if I have a Javascript variable like this:
var data = {
foo: "bar",
list: [1, 2, 3]
};
alert(data.foo); // "bar"
Let's say we need to write the content of the variable data and everything inside no matter the count, how deep or complex then how would we do this? If we try writing it to a text file:
var tempFile = File(Folder.desktop + "/tempFile.txt");
tempFile.open("w");
tempFile.write(data);
tempFile.close();
It would come out looking something like this:
{
foo: "bar",
list: [1, 2, 3]
}
That doesn't look bad and seems like it might be fine, but how do we get it out? How do we make it so that it's an object again?
var tempFile = File(Folder.desktop + "/tempFile.txt");
tempFile.open("r");
var data = tempFile.read();
tempFile.close();
alert(data); // "\{\r\n\tfoo:\s\"bar\",\r\n\tlist:\s[1,\s2,\s3]\r\n}"
alert(typeof data); // "String"
It looks a lot different when we try to retreive it and it's a string. How would we fix that? Well you could theoretically use eval() on it to try and evaluate it's value though eval is very dangerous (and deprecated specifically due to this) because you're trying to evaluate something you might not understand the value of or that value could be rewritten/reassigned just before evaluation in certain methods for hacking.
The way we solve for this entire problem is by using JSON:
var tempFile = File(Folder.desktop + "/tempFile.json");
tempFile.open("w");
tempFile.write(JSON.stringify(data)); // <== Note the difference here
tempFile.close();
// You cannot do this in scripting natively, you have to add JSON2.jsx or a similar polyfill.
This means our new file looks like this:
{
"foo": "bar",
"list": [1, 2, 3]
}
Your first reaction might be "That looks identical to the first time" and, well, yeah 😏 It's very close. The real difference is that I can re-integrate this value from an external source like a file very easily:
var tempFile = File(Folder.desktop + "/tempFile.json");
tempFile.open("r");
var data = tempFile.read();
tempFile.close();
alert(data); // "\{\r\n\tfoo:\s\"bar\",\r\n\tlist:\s[1,\s2,\s3]\r\n}"
alert(typeof data); // "String"
data = JSON.parse(data); // Because we can parse a JSON, it can become identical to it's original value.
alert(data); // "[Object Object]"
alert(data.foo); // "bar"
And the difference looks very minor here only because I'm using such a simple example. JSON can't handle every kind of Javascript value though; it can't store functions or RegExp primitives mostly for security reasons; but you can store any amount of basic data like objects, arrays, strings, numbers, evaluated expressions, and so on regardless of the depth or complexity of that data.
Copy link to clipboard
Copied
Might help to get a technical answer alongside an abstract one:
If Javascript were music, then JSON would be something like mp4 -- just a way to store that music in a way that computers can understand and make use of in the correct context.

