Contents
- The Problem
- Turning A Random Sub Layer On
- Randomising Colour
- Making The People Generator
- Full Script
The Problem
I recently had a very tight ask: to populate an image with hundreds of unique characters in under 2 weeks. This is naturally quite a tall order, so I broke out the javascript.
I have never used javascript on anything other than After Effects, but I was aware that you could write and run scripts on other adobe programs. I cracked open the scripting guide and figured that what I wanted wasn't too much of an ask.
All I needed to do was draw sections of a body which were interchangeable. I have played with this idea before, making dress up dolls in my teens using flash, and more recently playing with picrew. So I needed to write a script which was smart enough to say "on each layer, turn off all sub-layers, then turn one back on randomly." Bonus points if I could also make it randomise clothes and skin colour.
Here's how I achieved this.
Turning a random sub-layer on
I started testing my idea out on shapes. I knew if I could make a script work for 1 layer, I could make it work for multiple. I set up a document with 1 layer and 4 sub-layers, and drew a simple shape on each: a square, a circle, a star, and a triangle.
I decided to use sub-layers instead of groups/loose path items/compound paths because it meant I could keep artwork tidy, and my code could stay consistent.
I opened up adobe ExtendScript CC. I like to use this because it can plug straight into illustrator without having to save out a .jsx file.
The first thing I needed to do was tell the script to turn all the sub-layers off. This is to ensure that no sub-layers are left on when the script is run multiple times. I ended up creating a function using for loops to accomplish this:
var doc = app.activeDocument;
function hideAll() {
for (i = 0; i < doc.layers.length; i++) {
for (a = 0; a < doc.layers[i].layers.length; a++) {
doc.layers[i].layers[a].visible = false;
}
}
};
hideAll();
Here, I set up a variable for my active document, to avoid typing "app.activeDocument" repeatedly throughout the script. I then created my function "hideAll". The first for loop cycles through each layer in the document in turn, then stops when it has looped through all of them (using the argument "i < doc.layers.length" to set the number of layers, and "i++" to incrementally add 1 to the i value until the maximum layer number is reached). The second loops through all the sub-layers on each layer, then stops when it has looped through all of them (referencing sub-layers in illustrator simply requires repeated use of "layers[index]", which is why I chose this workflow). As the loop goes through each sub-layer, it sets the sub-layer's visibility to "false", turning it off.
Next I needed to script turning on a random sub-layer. I figured all I needed was a random variable, and to set the sub-layer's visibility to true. So I created a new function:
function showRandom() {
for (i = 0; i < doc.layers.length; i++) {
var randomLayerCount = Math.floor(Math.random() * doc.layers[i].layers.length);
doc.layers[i].layers[randomLayerCount].visible = true;
}
};
showRandom goes through each layer in the document, and creates a random number for the variable "randomLayerCount" between 0 and the number of sub-layers on the current layer. It uses "Math.random" to randomise a number between 0 and 1, multiplies that number by the number of sub-layers, and then finally uses "Math.floor" to round that number down to an integer. The for loop then turns on the sub-layer whose index number corresponds to the randomly generated number. I then run both my functions together:
hideAll();
showRandom();
To randomly turn on one of the shape sub-layers.
Randomising Colour
It works! Now to think about colours. If the people generator is going to truly work, it needs to be able to change skin tones, hair colours, clothes colours, etc.
I ended up creating colours within the script, rather than referencing swatches, in case I wanted to repurpose the script for another document. I created my colours like this:
var colour01 = new CMYKColor();
colour01.cyan = 3;
colour01.magenta = 11;
colour01.yellow = 18;
colour01.black = 0;
Since my document needed to be for print, I used CMYK instead of RGB (if you need to use RGB, just use "new RGBColor()" instead, and set each R G B colour value). I set the values for my new colour, and did this for each colour I needed to add to my script.
I then created an array for all the colours I wanted to cycle through. In this case, I wanted to cycle through 4 different skin tone options:
var skinTones = [colour01, colour02, colour03, colour04];
And a random number which could be used to select each colour:
var skinRandom = Math.floor(Math.random() * skinTones.length);
Now to colour the shapes. I decided that I wanted to colour all the shapes at once, should I want to adjust the image after the script was run. This meant I needed to turn on all the sub-layers, in order to make them valid targets for the script to select. I created a new function, the opposite of hideAll:
function showAll() {
for (i = 0; i < doc.layers.length; i++) {
for (a = 0; a < doc.layers[i].layers.length; a++) {
doc.layers[i].layers[a].visible = true;
}
}
};
Now all the sub-layers were on, I could go through all the shapes in turn and change their colours with a new function:
function colourAll() {
for (i = 0; i < doc.pathItems.length; i++) {
doc.pathItems[i].fillColor = skinTones[skinRandom];
}
};
This function goes through all the pathItems in the document, and changes their colour. I decided at this point to only use pathItems in my illustrator document going forward for the final artwork. What are pathItems specifically? They are any shapes which show up as "path" when drawn in illustrator:
This meant I had to remember not to use compound paths, patterns, or anything complex when drawing parts for my people I wanted the script to recolour later (or the script would not work).
I add my new functions to the end of my script:
showAll();
colourAll();
hideAll();
showRandom();
Now the script will turn all sub-layers on, colour all the pathItems a random colour from the array, turn all the sub-layers back off, then finally select 1 at random to turn back on. This may be over complicating things, but I found that, for me, this made the most logical sense.
Making The People Generator
From here, I had the basic building blocks I needed to make my people generator. Now I needed to think how this would work for multiple colour changes, and multiple layers. I created more shapes as a holding place for my finished artwork, across multiple layers and sub-layers.
Next I created 12 colours in total (adding to the 4 already created), to be my skin, hair and clothes colours. Then I create the arrays and random number generators, like so:
var skinTones = [colour01, colour02, colour03, colour04];
var skinRandom = Math.floor(Math.random() * skinTones.length);
var clothesTones = [colour05, colour06, colour07, colour08];
var clothesRandom01 = Math.floor(Math.random() * clothesTones.length);
var clothesRandom02 = Math.floor(Math.random() * clothesTones.length);
var clothesRandom03 = Math.floor(Math.random() * clothesTones.length);
var hairTones = [colour09, colour10, colour11, colour12];
var hairRandom = Math.floor(Math.random() * hairTones.length);
I gave clothes 3 separate random number generators, so the shirt, trousers, and shoe layers could all be different colours. Now that I had more than 1 colour array, I needed to go back and change my function "colourAll." I decided to break this into multiple functions: "colourSkin", "colourHair", "colourClothes01", "colourClothes02", and "colourClothes03." After this, I needed a way to specify which pathItem needed to be coloured with skin tones, which needed to be clothes, and which needed to be hair. I decided to do this by naming my pathItems in illustrator accordingly.
I then added an if statement to my new functions, so that they would only recolour pathItems whose names matched the function:
function colourSkin() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "skin") doc.pathItems[i].fillColor = skinTones[skinRandom];
}
};
function colourHair() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "hair") doc.pathItems[i].fillColor = hairTones[hairRandom];
}
};
function colourClothes01() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "clothes01") doc.pathItems[i].fillColor = clothesTones[clothesRandom01];
}
};
function colourClothes02() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "clothes02") doc.pathItems[i].fillColor = clothesTones[clothesRandom02];
}
};
function colourClothes03() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "clothes03") doc.pathItems[i].fillColor = clothesTones[clothesRandom03];
}
};
Then updated the functions at the end of the script again:
showAll();
colourSkin();
colourHair();
colourClothes01();
colourClothes02();
colourClothes03();
hideAll();
showRandom();
I was almost there! I had a script that could change the colours in the way that I wanted. But if I wanted to have my arms, legs, and hair layers separated out (which I need to to make the artwork overlap correctly) I needed to make one small change to my showRandom function. Otherwise each arm, hair and leg layer would be randomized separately, meaning mismatching sleeve lengths and clashing artwork!
Like with the colours, I split the function into multiple functions and used my layer names in order to label everything. If a layer's artwork could be independent, and did not need the same random number as another, I named it "unit." If a layer needed to match the same random number as another, I gave it and all other layers which matched it a consistent name, such as "arm," "leg," or "hair," and made sure each had the same number of sub-layers, in the same order I wanted them to be displayed.
Then, instead of the random variable being inside the for loop for these functions, I created it outside of the function entirely:
function showUnit() {
for (i = 0; i < doc.layers.length; i++) {
var randomUnitCount = Math.floor(Math.random() * doc.layers[i].layers.length);
if (doc.layers[i].name == "unit") doc.layers[i].layers[randomUnitCount].visible = true;
}
};
var randomHairCount = Math.floor(Math.random() * doc.layers["hair"].layers.length);
function showHair() {
for (i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name == "hair") doc.layers[i].layers[randomHairCount].visible = true;
}
};
var randomLegCount = Math.floor(Math.random() * doc.layers["leg"].layers.length);
function showLeg() {
for (i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name == "leg") doc.layers[i].layers[randomLegCount].visible = true;
}
};
var randomArmCount = Math.floor(Math.random() * doc.layers["arm"].layers.length);
function showArm() {
for (i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name == "arm") doc.layers[i].layers[randomArmCount].visible = true;
}
};
This meant the random number wasn't re-rolled every time the for loop was used. Once again, I updated my functions at the end of the script:
showAll();
colourSkin();
colourHair();
colourClothes01();
colourClothes02();
colourClothes03();
hideAll();
showUnit();
showHair();
showLeg();
showArm();
And it was finished! The basic script for randomising body parts in illustrator.
Thank you for staying with me through this entire process! I hope this breakdown has been useful. Please leave a comment if you have any questions, or ways to improve the process.
Full script:
var doc = app.activeDocument;
//colours
var colour01 = new CMYKColor();
colour01.cyan = 3;
colour01.magenta = 11;
colour01.yellow = 18;
colour01.black = 0;
var colour02 = new CMYKColor();
colour02.cyan = 9;
colour02.magenta = 29;
colour02.yellow = 46;
colour02.black = 0;
var colour03 = new CMYKColor();
colour03.cyan = 30;
colour03.magenta = 46;
colour03.yellow = 53;
colour03.black = 4;
var colour04 = new CMYKColor();
colour04.cyan = 37;
colour04.magenta = 77;
colour04.yellow = 100;
colour04.black = 46;
var colour05 = new CMYKColor();
colour05.cyan = 63;
colour05.magenta = 22;
colour05.yellow = 0;
colour05.black = 0;
var colour06 = new CMYKColor();
colour06.cyan = 55;
colour06.magenta = 0;
colour06.yellow = 96;
colour06.black = 0;
var colour07 = new CMYKColor();
colour07.cyan = 0;
colour07.magenta = 51;
colour07.yellow = 96;
colour07.black = 0;
var colour08 = new CMYKColor();
colour08.cyan = 0;
colour08.magenta = 96;
colour08.yellow = 89;
colour08.black = 0;
var colour09 = new CMYKColor();
colour09.cyan = 36;
colour09.magenta = 57;
colour09.yellow = 84;
colour09.black = 23;
var colour10 = new CMYKColor();
colour10.cyan = 5;
colour10.magenta = 0;
colour10.yellow = 93;
colour10.black = 0;
var colour11 = new CMYKColor();
colour11.cyan = 75;
colour11.magenta = 68;
colour11.yellow =67;
colour11.black = 90;
var colour12 = new CMYKColor();
colour12.cyan = 0;
colour12.magenta = 0;
colour12.yellow = 0;
colour12.black = 0;
var skinTones = [colour01, colour02, colour03, colour04];
var skinRandom = Math.floor(Math.random() * skinTones.length);
var clothesTones = [colour05, colour06, colour07, colour08];
var clothesRandom01 = Math.floor(Math.random() * clothesTones.length);
var clothesRandom02 = Math.floor(Math.random() * clothesTones.length);
var clothesRandom03 = Math.floor(Math.random() * clothesTones.length);
var hairTones = [colour09, colour10, colour11, colour12];
var hairRandom = Math.floor(Math.random() * hairTones.length);
//functions
function showAll() {
for (i = 0; i < doc.layers.length; i++) {
for (a = 0; a < doc.layers[i].layers.length; a++) {
doc.layers[i].layers[a].visible = true;
}
}
};
function colourSkin() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "skin") doc.pathItems[i].fillColor = skinTones[skinRandom];
}
};
function colourHair() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "hair") doc.pathItems[i].fillColor = hairTones[hairRandom];
}
};
function colourClothes01() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "clothes01") doc.pathItems[i].fillColor = clothesTones[clothesRandom01];
}
};
function colourClothes02() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "clothes02") doc.pathItems[i].fillColor = clothesTones[clothesRandom02];
}
};
function colourClothes03() {
for (i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].name == "clothes03") doc.pathItems[i].fillColor = clothesTones[clothesRandom03];
}
};
function hideAll() {
for (i = 0; i < doc.layers.length; i++) {
for (a = 0; a < doc.layers[i].layers.length; a++) {
doc.layers[i].layers[a].visible = false;
}
}
};
function showUnit() {
for (i = 0; i < doc.layers.length; i++) {
var randomUnitCount = Math.floor(Math.random() * doc.layers[i].layers.length);
if (doc.layers[i].name == "unit") doc.layers[i].layers[randomUnitCount].visible = true;
}
};
var randomHairCount = Math.floor(Math.random() * doc.layers["hair"].layers.length);
function showHair() {
for (i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name == "hair") doc.layers[i].layers[randomHairCount].visible = true;
}
};
var randomLegCount = Math.floor(Math.random() * doc.layers["leg"].layers.length);
function showLeg() {
for (i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name == "leg") doc.layers[i].layers[randomLegCount].visible = true;
}
};
var randomArmCount = Math.floor(Math.random() * doc.layers["arm"].layers.length);
function showArm() {
for (i = 0; i < doc.layers.length; i++) {
if (doc.layers[i].name == "arm") doc.layers[i].layers[randomArmCount].visible = true;
}
};
//run
showAll();
colourSkin();
colourHair();
colourClothes01();
colourClothes02();
colourClothes03();
hideAll();
showUnit();
showHair();
showLeg();
showArm();
Top comments (0)