1.算法程序
illustrator是矢量编辑软件,画板是绘制处理的重要容器,在印刷方面的一个重要功能就是直角圆角化,开发一个直角圆角化功能,以下功能仅用于学习交流,请勿用于非法用途和商业用途,源代码如下所示:
// Round Any Corner
// rounds selected corners of PathItems.
// Especially for the corners at the intersection point of curves,
// this script may work better than "Round Corners" filter (but slower).
// ## How To Use
// ## Illustrator CS
// 1. Select the anchor(s) or whole path(es) to round.
// 2. Run this script. A prompt window appears to set the rounding radius.
// Input the radius in point, then click OK.
// #. You can choose a behavior like Illustrator10 by change the setting.
// (-- see "setting" section below)
// ## Illustrator 10
// 1. Select the anchor(s) or whole path(es) to round
// WITH a foreground path that specifies the rounding radius.
// Half width of foreground path is used for the radius. (excluding stroke width)
// Using a circle is most suitable for intuitive understanding and ease of use.
// The script asks you to continue if there's a difference greater than 1 pt
// between width and height of foreground path.
// 2. Run this script. The foreground path is removed after rounding.
// #. When the number of selected path is 1, predefined radius is used
// for rounding. (-- see "setting" section below)
// ## Rounding Method
// Basically, the rounding method is compatible with the "Round Corners" filter.
// It is to add two anchors instead of the original anchor, at the points of
// specified line length from each selected corner. So if there're too many
// anchors on original path, this script can not round nicely.
// ## Radius
// Actually, the specified "radius" is not for a radius of arcs which drawn.
// It is for the line length from each selected corner and is for the base
// to compute the length of handles. The reason calling it "radius" is
// for compatibility with the "Round Corners" filter.
// This script does not round the corners which already rounded.
// (for example, select a circle and run this script does nothing)
// ### notice
// In the rounding process, the script merges anchors which nearly
// overlapped (when the distance between anchors is less than 0.05 points).
// This script does not work for some part of compound pathes.
// When this occurs, please select part of the compound path or release the compound path and
// select them, then run script again.
// I still have not figured out how to get properties from grouped pathes inside a compound path.
// JavaScript Script for Adobe Illustrator CS3
// Tested with Adobe Illustrator CS3 13.0.3, Windows XP SP2 (Japanese version).
// This script provided "as is" without warranty of any kind.
// Free to use and distribute.
// Change Log
// 2005-09-18 release on the web
// ...
// 2009-04-28 modified input part to accept unit and operator
// 2009-05-23 some refinements
var PMSLibs = confirm("此脚本会将路径直角调整为圆角\n--只会调整直角,路径不受任何影响(非软件自带效果可比)");
var ver10 = (version.indexOf('10') == 0);
roundAnyCorner();
function roundAnyCorner() {
// setting ----------------------------------------------
// -- rr : rounding radius ( unit : point )
// ## on IllustratorCS, this value is for default value in prompt window.
var rr = 10;
// -- use_foreground_shape_to_set_radius
// set this "true" to use half width of foreground path as rounding radius
// instead of set the radius with prompt window.
// ## on Illustrator10, this value is always true.
var use_foreground_shape_to_set_radius = false; // set true or false
// -- check_circle
// when use_foreground_shape_to_set_radius = true,
// setting this "true", the script asks you to continue if there's a difference
// greater than 1 pt between width and height of foreground path.
// it only checks difference between width and height.
var check_circle = true;
// ------------------------------------------------------
if (ver10) use_foreground_shape_to_set_radius = true;
var s = [];
getPathItemsInSelection(1, s); // extract pathItems which pathpoints length is greater than 1
if (s.length < 1) return;
// When use half width of foreground path as rounding radius
if (use_foreground_shape_to_set_radius) {
if (s.length < 2) {
// case : the number of selected path is 1
// ver10 -- round by predefined value
// CS -- ask radius by prompt window
if (!ver10) use_foreground_shape_to_set_radius = false;
} else {
// case : the number of selected path > 1
var pi = s.shift(); // remove foreground path from list of selection
var rr2 = getRadius(pi, check_circle); // get half width of it
if (rr2 < 0) return;
if (rr2 > 0) {
rr = rr2;
pi.remove(); // remove the path
} else {
// case : the width of foreground path is 0
// ver10 -- round by predefined value. the foreground path remains.
// CS -- ask radius by prompt window the foreground path remains.
if (!ver10) use_foreground_shape_to_set_radius = false;
}
}
}
if (!use_foreground_shape_to_set_radius) {
rr = prompt("输入圆角半径\r-- 可以使用的单位有pt(默认)、mm、in\r-- 也可以进行四则运算(+ - * /)", rr); // input the radius;
if (!rr) return;
rr = rr.replace(/mm/ig, "*2.83464567");
rr = rr.replace(/pt/ig, "");
rr = rr.replace(/in/ig, "*72");
rr = rr.replace(/\s+/g, "");
try {
var eval_rr = eval(rr);
} catch (e) {
alert("ERROR:\n" + e.description);
return;
}
if (isNaN(eval_rr) || eval_rr <= 0) {
alert("\n警告:错误的四则计算公式!计算结果必须为正值。");
return;
}
rr = eval_rr;
}
//var tim = new Date();
var p, op, pnts;
var skipList, adjRdirAtEnd, redrawFlg;
var i, nxi, pvi, q, d, ds, r, g, t, qb;
var anc1, ldir1, rdir1, anc2, ldir2, rdir2;
var hanLen = 4 * (Math.sqrt(2) - 1) / 3;
var ptyp = PointType.SMOOTH;
for (var j = 0; j < s.length; j++) {
p = s[j].pathPoints;
if (readjustAnchors(p) < 2) continue; // reduce anchors
op = !s[j].closed;
pnts = op ? [getDat(p[0])] : [];
redrawFlg = false;
adjRdirAtEnd = 0;
skipList = [(op || !isSelected(p[0]) || !isCorner(p, 0))];
for (i = 1; i < p.length; i++) {
skipList.push((!isSelected(p[i])
|| !isCorner(p, i)
|| (op && i == p.length - 1)));
}
for (i = 0; i < p.length; i++) {
nxi = parseIdx(p, i + 1);
if (nxi < 0) break;
pvi = parseIdx(p, i - 1);
q = [p[i].anchor, p[i].rightDirection,
p[nxi].leftDirection, p[nxi].anchor];
ds = dist(q[0], q[3]) / 2;
if (arrEq(q[0], q[1]) && arrEq(q[2], q[3])) { // straight side
r = Math.min(ds, rr);
g = getRad(q[0], q[3]);
anc1 = getPnt(q[0], g, r);
ldir1 = getPnt(anc1, g + Math.PI, r * hanLen);
if (skipList[nxi]) {
if (!skipList[i]) {
pnts.push([anc1, anc1, ldir1, ptyp]);
redrawFlg = true;
}
pnts.push(getDat(p[nxi]));
} else {
if (r < rr) { // when the length of the side is less than rr * 2
pnts.push([anc1,
getPnt(anc1, getRad(ldir1, anc1), r * hanLen),
ldir1,
ptyp]);
} else {
if (!skipList[i]) pnts.push([anc1, anc1, ldir1, ptyp]);
anc2 = getPnt(q[3], g + Math.PI, r);
pnts.push([anc2,
getPnt(anc2, g, r * hanLen),
anc2,
ptyp]);
}
redrawFlg = true;
}
} else { // not straight side
d = getT4Len(q, 0) / 2;
r = Math.min(d, rr);
t = getT4Len(q, r);
anc1 = bezier(q, t);
rdir1 = defHan(t, q, 1);
ldir1 = getPnt(anc1, getRad(rdir1, anc1), r * hanLen);
if (skipList[nxi]) {
if (skipList[i]) {
pnts.push(getDat(p[nxi]));
} else {
pnts.push([anc1, rdir1, ldir1, ptyp]);
with (p[nxi]) pnts.push([anchor,
rightDirection,
adjHan(anchor, leftDirection, 1 - t),
ptyp]);
redrawFlg = true;
}
} else { // skipList[nxi] = false
if (r < rr) { // the length of the side is less than rr * 2
if (skipList[i]) {
if (!op && i == 0) {
adjRdirAtEnd = t;
} else {
pnts[pnts.length - 1][1] = adjHan(q[0], q[1], t);
}
pnts.push([anc1,
getPnt(anc1, getRad(ldir1, anc1), r * hanLen),
defHan(t, q, 0),
ptyp]);
} else {
pnts.push([anc1,
getPnt(anc1, getRad(ldir1, anc1), r * hanLen),
ldir1,
ptyp]);
}
} else { // round the corner with the radius rr
if (skipList[i]) {
t = getT4Len(q, -r);
anc2 = bezier(q, t);
if (!op && i == 0) {
adjRdirAtEnd = t;
} else {
pnts[pnts.length - 1][1] = adjHan(q[0], q[1], t);
}
ldir2 = defHan(t, q, 0);
rdir2 = getPnt(anc2, getRad(ldir2, anc2), r * hanLen);
pnts.push([anc2, rdir2, ldir2, ptyp]);
} else {
qb = [anc1, rdir1, adjHan(q[3], q[2], 1 - t), q[3]];
t = getT4Len(qb, -r);
anc2 = bezier(qb, t);
ldir2 = defHan(t, qb, 0);
rdir2 = getPnt(anc2, getRad(ldir2, anc2), r * hanLen);
rdir1 = adjHan(anc1, rdir1, t);
pnts.push([anc1, rdir1, ldir1, ptyp],
[anc2, rdir2, ldir2, ptyp]);
}
}
redrawFlg = true;
}
}
}
if (adjRdirAtEnd > 0) {
pnts[pnts.length - 1][1] = adjHan(p[0].anchor, p[0].rightDirection, adjRdirAtEnd);
}
if (redrawFlg) {
// redraw
for (i = p.length - 1; i > 0; i--) p[i].remove();
for (i = 0; i < pnts.length; i++) {
pt = i > 0 ? p.add() : p[0];
with (pt) {
anchor = pnts[i][0];
rightDirection = pnts[i][1];
leftDirection = pnts[i][2];
pointType = pnts[i][3];
}
}
}
}
activeDocument.selection = s;
// alert(new Date() - tim);
}
// ------------------------------------------------
function getRadius(pi, check_circle) {
with (pi) {
var gb = geometricBounds;
var w = (gb[2] - gb[0]);
var h = (gb[1] - gb[3]);
if (check_circle && Math.abs(w - h) > 1
&& !confirm("There's a difference between width and\n"
+ "height of foreground path. Continue?")) {
return -1;
}
return w / 2;
}
}
// ------------------------------------------------
// return [x,y] of the distance "len" and the angle "rad"(in radian)
// from "pt"=[x,y]
function getPnt(pt, rad, len) {
return [pt[0] + Math.cos(rad) * len,
pt[1] + Math.sin(rad) * len];
}
// ------------------------------------------------
// return the [x, y] coordinate of the handle of the point on the bezier curve
// that corresponds to the parameter "t"
// n=0:leftDir, n=1:rightDir
function defHan(t, q, n) {
return [t * (t * (q[n][0] - 2 * q[n + 1][0] + q[n + 2][0]) + 2 * (q[n + 1][0] - q[n][0])) + q[n][0],
t * (t * (q[n][1] - 2 * q[n + 1][1] + q[n + 2][1]) + 2 * (q[n + 1][1] - q[n][1])) + q[n][1]];
}
// -----------------------------------------------
// return the [x, y] coordinate on the bezier curve
// that corresponds to the paramter "t"
function bezier(q, t) {
var u = 1 - t;
return [u * u * u * q[0][0] + 3 * u * t * (u * q[1][0] + t * q[2][0]) + t * t * t * q[3][0],
u * u * u * q[0][1] + 3 * u * t * (u * q[1][1] + t * q[2][1]) + t * t * t * q[3][1]];
}
// ------------------------------------------------
// adjust the length of the handle "dir"
// by the magnification ratio "m",
// returns the modified [x, y] coordinate of the handle
// "anc" is the anchor [x, y]
function adjHan(anc, dir, m) {
return [anc[0] + (dir[0] - anc[0]) * m,
anc[1] + (dir[1] - anc[1]) * m];
}
// ------------------------------------------------
// return true if the pathPoints "p[idx]" is a corner
function isCorner(p, idx) {
var pnt0 = getAnglePnt(p, idx, -1);
var pnt1 = getAnglePnt(p, idx, 1);
if (!pnt0 || !pnt1) return false; // at the end of a open-path
if (pnt0.length < 1 || pnt1.length < 1) return false; // anchor is overlapping, so cannot determine the angle
var rad = getRad2(pnt0, p[idx].anchor, pnt1, true);
if (rad > Math.PI - 0.1) return false; // set the angle tolerance here
return true;
}
// ------------------------------------------------
// "p"=pathPoints, "idx1"=index of pathpoint
// "dir" = -1, returns previous point [x,y] to get the angle of tangent at pathpoints[idx1]
// "dir" = 1, returns next ...
function getAnglePnt(p, idx1, dir) {
if (!dir) dir = -1;
var idx2 = parseIdx(p, idx1 + dir);
if (idx2 < 0) return null; // at the end of a open-path
var p2 = p[idx2];
with (p[idx1]) {
if (dir < 0) {
if (arrEq(leftDirection, anchor)) {
if (arrEq(p2.anchor, anchor)) return [];
if (arrEq(p2.anchor, p2.rightDirection)
|| arrEq(p2.rightDirection, anchor)) return p2.anchor;
else return p2.rightDirection;
} else {
return leftDirection;
}
} else {
if (arrEq(anchor, rightDirection)) {
if (arrEq(anchor, p2.anchor)) return [];
if (arrEq(p2.anchor, p2.leftDirection)
|| arrEq(anchor, p2.leftDirection)) return p2.anchor;
else return p2.leftDirection;
} else {
return rightDirection;
}
}
}
}
// --------------------------------------
// if the contents of both arrays are equal, return true (lengthes must be same)
function arrEq(arr1, arr2) {
for (var i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) return false;
}
return true;
}
// ------------------------------------------------
// return the distance between p1=[x,y] and p2=[x,y]
function dist(p1, p2) {
return Math.sqrt(Math.pow(p1[0] - p2[0], 2)
+ Math.pow(p1[1] - p2[1], 2));
}
// ------------------------------------------------
// return the squared distance between p1=[x,y] and p2=[x,y]
function dist2(p1, p2) {
return Math.pow(p1[0] - p2[0], 2)
+ Math.pow(p1[1] - p2[1], 2);
}
// --------------------------------------
// return the angle in radian
// of the line drawn from p1=[x,y] from p2
function getRad(p1, p2) {
return Math.atan2(p2[1] - p1[1],
p2[0] - p1[0]);
}
// --------------------------------------
// return the angle between two line segments
// o-p1 and o-p2 ( 0 - Math.PI)
function getRad2(p1, o, p2) {
var v1 = normalize(p1, o);
var v2 = normalize(p2, o);
return Math.acos(v1[0] * v2[0] + v1[1] * v2[1]);
}
// ------------------------------------------------
function normalize(p, o) {
var d = dist(p, o);
return d == 0 ? [0, 0] : [(p[0] - o[0]) / d,
(p[1] - o[1]) / d];
}
// ------------------------------------------------
// return the bezier curve parameter "t"
// at the point which the length of the bezier curve segment
// (from the point start drawing) is "len"
// when "len" is 0, return the length of whole this segment.
function getT4Len(q, len) {
var m = [q[3][0] - q[0][0] + 3 * (q[1][0] - q[2][0]),
q[0][0] - 2 * q[1][0] + q[2][0],
q[1][0] - q[0][0]];
var n = [q[3][1] - q[0][1] + 3 * (q[1][1] - q[2][1]),
q[0][1] - 2 * q[1][1] + q[2][1],
q[1][1] - q[0][1]];
var k = [m[0] * m[0] + n[0] * n[0],
4 * (m[0] * m[1] + n[0] * n[1]),
2 * ((m[0] * m[2] + n[0] * n[2]) + 2 * (m[1] * m[1] + n[1] * n[1])),
4 * (m[1] * m[2] + n[1] * n[2]),
m[2] * m[2] + n[2] * n[2]];
var fullLen = getLength(k, 1);
if (len == 0) {
return fullLen;
} else if (len < 0) {
len += fullLen;
if (len < 0) return 0;
} else if (len > fullLen) {
return 1;
}
var t, d;
var t0 = 0;
var t1 = 1;
var torelance = 0.001;
for (var h = 1; h < 30; h++) {
t = t0 + (t1 - t0) / 2;
d = len - getLength(k, t);
if (Math.abs(d) < torelance) break;
else if (d < 0) t1 = t;
else t0 = t;
}
return t;
}
// ------------------------------------------------
// return the length of bezier curve segment
// in range of parameter from 0 to "t"
function getLength(k, t) {
var h = t / 128;
var hh = h * 2;
var fc = function (t, k) {
return Math.sqrt(t * (t * (t * (t * k[0] + k[1]) + k[2]) + k[3]) + k[4]) || 0
};
var total = (fc(0, k) - fc(t, k)) / 2;
for (var i = h; i < t; i += hh) total += 2 * fc(i, k) + fc(i + h, k);
return total * hh;
}
// ------------------------------------------------
// extract PathItems from the selection which length of PathPoints
// is greater than "n"
function getPathItemsInSelection(n, pathes) {
if (documents.length < 1) return;
var s = activeDocument.selection;
if (!(s instanceof Array) || s.length < 1) return;
extractPathes(s, n, pathes);
}
// --------------------------------------
// extract PathItems from "s" (Array of PageItems -- ex. selection),
// and put them into an Array "pathes". If "pp_length_limit" is specified,
// this function extracts PathItems which PathPoints length is greater
// than this number.
function extractPathes(s, pp_length_limit, pathes) {
for (var i = 0; i < s.length; i++) {
if (s[i].typename == "PathItem") {
if (pp_length_limit
&& s[i].pathPoints.length <= pp_length_limit) {
continue;
}
pathes.push(s[i]);
} else if (s[i].typename == "GroupItem") {
// search for PathItems in GroupItem, recursively
extractPathes(s[i].pageItems, pp_length_limit, pathes);
} else if (s[i].typename == "CompoundPathItem") {
// searches for pathitems in CompoundPathItem, recursively
// ( ### Grouped PathItems in CompoundPathItem are ignored ### )
extractPathes(s[i].pathItems, pp_length_limit, pathes);
}
}
}
// --------------------------------------
// merge nearly overlapped anchor points
// return the length of pathpoints after merging
function readjustAnchors(p) {
// Settings ==========================
// merge the anchor points when the distance between
// 2 points is within ### square root ### of this value (in point)
var minDist = 0.0025;
// ===================================
if (p.length < 2) return 1;
var i;
if (p.parent.closed) {
for (i = p.length - 1; i >= 1; i--) {
if (dist2(p[0].anchor, p[i].anchor) < minDist) {
p[0].leftDirection = p[i].leftDirection;
p[i].remove();
} else {
break;
}
}
}
for (i = p.length - 1; i >= 1; i--) {
if (dist2(p[i].anchor, p[i - 1].anchor) < minDist) {
p[i - 1].rightDirection = p[i].rightDirection;
p[i].remove();
}
}
return p.length;
}
// -----------------------------------------------
// return pathpoint's index. when the argument is out of bounds,
// fixes it if the path is closed (ex. next of last index is 0),
// or return -1 if the path is not closed.
function parseIdx(p, n) { // PathPoints, number for index
var len = p.length;
if (p.parent.closed) {
return n >= 0 ? n % len : len - Math.abs(n % len);
} else {
return (n < 0 || n > len - 1) ? -1 : n;
}
}
// -----------------------------------------------
function getDat(p) { // pathPoint
with (p) return [anchor, rightDirection, leftDirection, pointType];
}
// -----------------------------------------------
function isSelected(p) { // PathPoint
return p.selected == PathPointSelection.ANCHORPOINT;
}
2.作者答疑
如有疑问,请留言。
提示: 作者知了-联系方式1
提示: 作者知了-联系方式2