Copy link to clipboard
Copied
The basic idea that I want to try is this:
Given a series of elements, I want to create a vertically striped mask that will cover all of the elements with some overlap on either side.
Each of the vertical stripes will be placed "C" pixels apart from each other horizontally where C=number of images. The vertical stripes will cover all of the images.
How I do it now, is I create a compound path of vertical 1px wide stripes separated by "C" pixels for the entire width (plus 2xC for overlap). I then place a copy of the compound path aligned with the first image (offset horizontally by "-C" pixels) and select both the compound path and the layer to create a clipping mask.
I repeat this for each shape, but offset the compound path by 1 px horizontally each times (for a total of "C" clipping masks.
I have tried to duplicate this in the code below but I'm not that great at JS so I used the Jquery library (which I am also not that great at). I fgure there are easier ways to accomplish this, so please let me know. I am famous for making things much harder than they need to be.
Thanks for taking the time to consider this.
//PLEASE REMEMBER THIS IS PSUEDO CODE USING JQUERY-ishness
//There may be much better ways to do with the Illustrator API.
//assumes a series of shapes of class shape
/* Overview: Given a series of shapes, find the min and max x and y coordinates to determine the sie of the mask (subtract the count of shapes from min and and to max for overlap of mask).
Draw a series of rectangles 1px wide every (shapecount) pixels for the entire width.
Combine those into a compound shape (no idea how)
For each shape, clone the mask compound shape and place it above the shape. Select the shape and the compound shape and make a clipping mask.
DONE!*/
//getDims -- simple util for getting the x,y,w,h for all shapes combined
function getDims(lst) {
let xmin = 0,
ymin = 0,
xmax = 0,
ymax = 0;
lst.each(function () {
let elem = this;
let dims = elem.getBoundingClientRect();
xmin = Math.floor(Math.min(xmin, dims.left));
ymin = Math.floor(Math.min(ymin, dims.top));
xmax = Math.ciel(Math.max(dims.right, xmax));
ymax = Math.ciel(Math.max(dims.bottom, ymax));
});
return { x: xmin, y: ymin, w: xmax - xmin, h: ymax - ymin };
}
let shapesList = $(".shape");
let c = shapesList.length;
let dims = getDims(shapesList);
let w = dims.width + 2 * c;
let h = dims.height + 2 * c;
//simple function for building the mask svg element to cover the width/height with vertical stripes separated by the number of pixels=count of images
function buildMask() {
let maskSVG =
"<svg height='" +
h +
"' width='" +
w +
"' viewBox='0 0 " +
w +
" " +
h +
"'> ";
//interior function to repeat the lenticule
function drawLent(x) {
maskSVG +=
"<rect x='" + x + "' width='1' height='" + h + "' fill='#000000'/>";
}
let x = 0;
let lc = Math.floor(w / c);
for (let i = 0; i < lc; i++) {
x += c * i;
drawLent(x);
}
maskSVG += "</svg>";
let mask = $("<div></div>");
mask.innerHTML(maskSVG);
return mask;
}
let mask = buildMask();
shapesList.each(function (i) {
let shape = $(this);
let shapeMask = mask.clone();
shapeMask.offset({ left: i + (dims.x - c), top: dims.y });
let z = shape.css("z-index");
z -= 1;
shapeMask.css("z-index", z);
//do something magical to define a mask using shapeMask and shape
//probably something like shape.applyMask(shapeMask);
});
});
Hi @Jason Burnett, I made this script, that *maybe* does something like what you are asking. Basically clips each selected page item with a compound path made of vertical bars spaced out according to (a) settings.barWidth, and (b) the number of page items selected. Select a few items and try it.
- Mark
With 3 Items selected.
With 6 Items selected. And changing the barWidth to 0.25
/**
* @file Add Clipping Bars.js
*
* Usage:
* 0. Make adjustments to the `settings` object below, if necessar
...
Copy link to clipboard
Copied
Hi @Jason Burnett, can you show us a visual of the expected result? Or even better, a sample file we can look at?
- Mark
Copy link to clipboard
Copied
I do not understand your question entirely. Can you sketch it.
so far I understood, the solution will be found in the use of compound paths and masks.
Copy link to clipboard
Copied
Hi @Jason Burnett, I made this script, that *maybe* does something like what you are asking. Basically clips each selected page item with a compound path made of vertical bars spaced out according to (a) settings.barWidth, and (b) the number of page items selected. Select a few items and try it.
- Mark
With 3 Items selected.
With 6 Items selected. And changing the barWidth to 0.25
/**
* @file Add Clipping Bars.js
*
* Usage:
* 0. Make adjustments to the `settings` object below, if necessary.
* 1. Select multiple page items in Illustrator.
* 2. Run script.
*
* @author m1b
* @version 2024-09-05
* @discussion https://community.adobe.com/t5/illustrator-discussions/help-creating-script-from-pseudo-code/m-p/14842810
*/
(function () {
var settings = {
// only set this if you want to force the number of slices
sliceCount: undefined,
// the width of each bar, in points
barWidth: 1,
// if you want to choose the bar width each time
showUI: false,
};
if (
0 === app.documents.length
|| 0 === app.activeDocument.selection.length
)
return alert('Please select some items and try again.');
if (settings.showUI) {
var barWidth = Number(prompt('Width of each bar:', settings.barWidth || 1));
if (isNaN(barWidth))
return;
else
settings.barWidth = barWidth;
}
var doc = app.activeDocument,
items = doc.selection;
settings.sliceCount = Math.min(settings.sliceCount || Infinity, items.length);
// calculate the outer bounds
var everyBound = [];
for (var i = settings.sliceCount - 1; i >= 0; i--)
everyBound.push(getItemBounds(items[i]));
var outerBounds = combineBounds(everyBound),
step = settings.sliceCount * settings.barWidth;
// add margins
outerBounds[0] -= step;
outerBounds[1] += step;
outerBounds[2] += step;
outerBounds[3] -= step;
// add the clipping bars
for (var i = settings.sliceCount - 1; i >= 0; i--)
addClippingBars(items[i], outerBounds, i, settings.sliceCount, settings.barWidth);
})();
/**
* Adds vertical bars compound path to clip `item`.
* @author m1b
* @version 2024-09-05
* @param {PageItem} item - an Illustrator PageItem.
* @param {bounds} bounds - the outer bounds [l,t,r,b].
* @param {Number} index - the slice index.
* @param {Number} sliceCount - the number of slices.
* @param {Number} barWidth - the width of each bar.
*/
function addClippingBars(item, bounds, index, sliceCount, barWidth) {
var x = 0,
step = sliceCount * barWidth;
var container = item.parent,
bars = container.compoundPathItems.add(),
fullLength = Math.ceil((bounds[2] - bounds[0]) / step) * step;
while (x <= fullLength) {
drawBar(bars, bounds, x + (index*barWidth), barWidth);
x += step;
}
// do the clipping
var group = bars.parent.groupItems.add();
bars.move(group, ElementPlacement.PLACEATEND);
item.move(group, ElementPlacement.PLACEATEND);
// cannot create the clipping mask with code, so ...
app.selection = [group];
app.executeMenuCommand('makeMask');
return bars;
/**
* Draws one bar.
* @param {Document|GroupItem|Layer} container - the place to draw the bar.
* @param {bounds} bounds - the outer bounds [l,t,b,r].
* @param {Number} x - the x offset, in points.
* @param {Number} barWidth - the bar width, in points.
*/
function drawBar(container, bounds, x, barWidth) {
return drawRectangle(container,
[
bounds[0] + x,
bounds[1],
bounds[0] + x + barWidth,
bounds[3],
]
);
};
};
/**
* Draws a rectangle to the document.
* @param {Document|Layer|GroupItem} container - an Illustrator container.
* @param {Array<Number>} bounds - [T, L, B, R]
* @param {Object} props - properties to assign to the rectangle.
* @return {PathItem}
*/
function drawRectangle(container, bounds, properties) {
properties = properties || {};
var rectangle = container.pathItems.rectangle(bounds[1], bounds[0], bounds[2] - bounds[0], -(bounds[3] - bounds[1])); // TLWH
// defaults
rectangle.filled = true;
rectangle.stroked = false;
// apply properties
for (var key in properties)
if (properties.hasOwnProperty(key))
rectangle[key] = properties[key];
return rectangle;
};
/**
* Returns bounds of item(s).
* @author m1b
* @version 2024-03-10
* @param {PageItem|Array<PageItem>} item - an Illustrator PageItem or array of PageItems.
* @param {Boolean} [geometric] - if false, returns visible bounds.
* @param {Array} [bounds] - private parameter, used when recursing.
* @returns {Array} - the calculated bounds.
*/
function getItemBounds(item, geometric, bounds) {
var newBounds = [],
boundsKey = geometric ? 'geometricBounds' : 'visibleBounds';
if (undefined == item)
return;
if (
item.typename == 'GroupItem'
|| item.constructor.name == 'Array'
) {
var children = item.typename == 'GroupItem' ? item.pageItems : item,
contentBounds = [],
isClippingGroup = (item.hasOwnProperty('clipped') && item.clipped == true),
clipBounds;
for (var i = 0, child; i < children.length; i++) {
child = children[i];
if (
child.hasOwnProperty('clipping')
&& true === child.clipping
)
// the clipping item
clipBounds = child.geometricBounds;
else
contentBounds.push(getItemBounds(child, geometric, bounds));
}
newBounds = combineBounds(contentBounds);
if (isClippingGroup)
newBounds = intersectionOfBounds([clipBounds, newBounds]);
}
else if (item.typename == 'TextFrame') {
// get bounds of outlined text
var dup = item.duplicate().createOutline();
newBounds = dup[boundsKey];
dup.remove();
}
else if (item.hasOwnProperty(boundsKey)) {
newBounds = item[boundsKey];
}
// `bounds` will exist if this is a recursive execution
bounds = (undefined == bounds)
? bounds = newBounds
: bounds = combineBounds([newBounds, bounds]);
return bounds;
};
/**
* Returns the combined bounds of all bounds supplied.
* Works with Illustrator or Indesign bounds.
* @author m1b
* @version 2024-03-09
* @param {Array<bounds>} boundsArray - an array of bounds [L, T, R, B] or [T, L , B, R].
* @returns {bounds?} - the combined bounds.
*/
function combineBounds(boundsArray) {
var combinedBounds = boundsArray[0],
comparator;
if (/indesign/i.test(app.name))
comparator = [Math.min, Math.min, Math.max, Math.max];
else if (/illustrator/i.test(app.name))
comparator = [Math.min, Math.max, Math.max, Math.min];
// iterate through the rest of the bounds
for (var i = 1; i < boundsArray.length; i++) {
var bounds = boundsArray[i];
combinedBounds = [
comparator[0](combinedBounds[0], bounds[0]),
comparator[1](combinedBounds[1], bounds[1]),
comparator[2](combinedBounds[2], bounds[2]),
comparator[3](combinedBounds[3], bounds[3]),
];
}
return combinedBounds;
};
/**
* Returns the overlapping rectangle
* of two or more rectangles.
* NOTE: Returns undefined if ANY
* rectangles do not intersect.
* @author m1b
* @version 2024-09-05
* @param {Array<bounds>} arrayOfBounds - an array of bounds [L, T, R, B] or [T, L , B, R].
* @returns {bounds?} - intersecting bounds.
*/
function intersectionOfBounds(arrayOfBounds) {
var comparator;
if (/indesign/i.test(app.name))
comparator = [Math.max, Math.max, Math.min, Math.min];
else if (/illustrator/i.test(app.name))
comparator = [Math.max, Math.min, Math.min, Math.max];
// sort a copy of array
var bounds = arrayOfBounds
.slice(0)
.sort(function (a, b) { return b[0] - a[0] || a[1] - b[1] });
// start with first bounds
var intersection = bounds.shift(),
b;
// compare each bounds, getting smaller
while (b = bounds.shift()) {
// if doesn't intersect, bail out
if (!Mittens.boundsDoIntersect(intersection, b))
return;
intersection = [
comparator[0](intersection[0], b[0]),
comparator[1](intersection[1], b[1]),
comparator[2](intersection[2], b[2]),
comparator[3](intersection[3], b[3]),
];
}
return intersection;
};
/**
* Returns true if the two bounds intersect.
* @author m1b
* @version 2024-03-10
* @param {Array} bounds1 - bounds array.
* @param {Array} bounds2 - bounds array.
* @param {Boolean} [TLBR] - whether bounds arrays are interpreted as [t, l, b, r] or [l, t, r, b] (default: based on app).
* @returns {Boolean}
*/
function boundsDoIntersect(bounds1, bounds2, TLBR) {
if (undefined == TLBR)
TLBR = (/indesign/i.test(app.name));
return !(
TLBR
// TLBR
? (
bounds2[0] > bounds1[2]
|| bounds2[1] > bounds1[3]
|| bounds2[2] < bounds1[0]
|| bounds2[3] < bounds1[1]
)
// LTRB
: (
bounds2[0] > bounds1[2]
|| bounds2[1] < bounds1[3]
|| bounds2[2] < bounds1[0]
|| bounds2[3] > bounds1[1]
)
);
};
Copy link to clipboard
Copied
WOW. I got so excited to play with this code that I didn't even get the chance to reply earlier. This is great. There's a lot of great information in here about how to create masks and shapes. Thank you so much. This is really wonderful.
Copy link to clipboard
Copied
Great to hear it was helpful! Yeah ExtendScript is like JavaScript from 1999 with a few quirks of its own mixed in. All the best with your project!
- Mark