Ce script découpe l'image du calque courant en tranches verticales, puis dispose les tranches en éventail en les ajustant à la dimension voulue.
Les tranches sont séparées par une marge et possèdent des coins arrondis.
L'image doit plutot etre en format paysage et assez allongée pour pouvoir etre disposée en un bel éventail.
Une interface utilisateur permet de définir les paramètres de cette transformation:
Nombre de morceaux, angle total, taille du cercle, marge entre morceaux, rayon de l'arrondi des coins.
Le script plus bas rassemble trois parties regroupées ici en une seule (PATHS, TRANSFRM et FAN), mais peut etre copié dans un seul fichier FAN.JSX.
NOTE: Par modularité, il peut-etre utile de séparer ces parties en trois fichiers : PATHS.JSX, TRANSFRM.JSX et FAN.JSX.
Dans ce cas, les lignes "#include ..." au début de FAN.JSX doivent etre activées.
C'est ensuite FAN.JSX qui doit etre exécuté.
/************************************************************************
Fan
************************************************************************
Build a fan with a wide image.
************************************************************************
13/03/2010 V1r02b Habaki: Improve dialog
12/03/2010 V1r02 Habaki: Add the fan angle
09/03/2010 V1r01 Habaki: Creation
************************************************************************/
/************************************************************************
PATHS.JSX
************************************************************************
09/03/2010 Habaki V1r01 : Creation
************************************************************************/
/*----------------------------------------------------------------------*
New Path on a rectangle
with Round corners
*----------------------------------------------------------------------*/
function PathRect(
Top, Left,Bottom,Right,
CornerRadius
)
{
var idPxl = charIDToTypeID( "#Pxl" );
var idsetd = charIDToTypeID( "setd" );
var idnull = charIDToTypeID( "null" );
var idPath = charIDToTypeID( "Path" );
var idWrPt = charIDToTypeID( "WrPt" );
var idT = charIDToTypeID( "T " );
//var idT = charIDToTypeID( "TyPa" );
var idTop = charIDToTypeID( "Top " );
var idLeft = charIDToTypeID( "Left" );
var idBtom = charIDToTypeID( "Btom" );
var idRght = charIDToTypeID( "Rght" );
var idRds = charIDToTypeID( "Rds " );
var idRctn = charIDToTypeID( "Rctn" );
var ref4 = new ActionReference();
ref4.putProperty( idPath, idWrPt );
var desc0 = new ActionDescriptor();
desc0.putReference( idnull, ref4 );
var desc1 = new ActionDescriptor();
desc1.putUnitDouble( idTop, idPxl, Top);
desc1.putUnitDouble( idLeft, idPxl, Left);
desc1.putUnitDouble( idBtom, idPxl, Bottom);
desc1.putUnitDouble( idRght, idPxl, Right);
desc1.putUnitDouble( idRds, idPxl, CornerRadius);
desc0.putObject( idT, idRctn, desc1 );
executeAction( idsetd, desc0, DialogModes.NO );
}
/*----------------------------------------------------------------------*/
/************************************************************************
TRANSFORM.JSX
Use Warp
************************************************************************
Perform several transformations of a rectangle :
Identity
Trapezium
First the transform grid must be computed. Then the warp use the last
computed grid.
************************************************************************
07/03/2010 Habaki V1r01 : Creation
************************************************************************/
/*----------------------------------------------------------------------*
Constructor
*----------------------------------------------------------------------*/
function Transform()
{
this.lCur = app.activeDocument.activeLayer;
var Bounds = this.lCur.bounds;
this.top = Bounds[1];
this.left = Bounds[0];
this.bottom = Bounds[3];
this.right = Bounds[2];
// Grid nb of segments
this.gridHNb = 3;
this.gridVNb = 3;
// Grid Unit
this.gridHU = (this.right - this.left) / this.gridHNb;
this.gridVU = (this.bottom - this.top) / this.gridVNb;
// Warp grip : [V][H] : 0 1 2 3 / 4 5 6 7/ ...
this.grid;
}
/*----------------------------------------------------------------------*
Identity Grid
*----------------------------------------------------------------------*/
Transform.prototype.gridIdentity = function()
{
var grid = new Array();
var pt, line;
var h, v;
for (v = 0;v <= this.gridVNb;v++) {
line = new Array();
for (h = 0;h <= this.gridHNb;h++) {
pt = new Array();
pt[0] = this.left + h* this.gridHU;
pt[1] = this.top + v* this.gridVU;
line[h] = pt;
}
grid[v] = line;
}
return(grid);
}
/*----------------------------------------------------------------------*
Trapezium grid
*----------------------------------------------------------------------*/
Transform.prototype.gridTrapez = function(
ScaleBottom // Scale of bottom edge (%)
)
{
var grid = new Array();
var pt, line;
var h, v;
var H, V;
ScaleBottom /= 100.0;
ScaleBottom = 1.0 - ScaleBottom;
for (v = 0;v <= this.gridVNb;v++) {
line = new Array();
for (h = 0;h <= this.gridHNb;h++) {
pt = new Array();
H = 1.0*h / this.gridHNb;
V = 1.0*v / this.gridVNb;
pt[0] = this.left
+ this.gridHU * this.gridHNb*(0.5 -
(0.5 - H)*(1.0 - V*ScaleBottom));
pt[1] = this.top + this.gridVU * v;
line[h] = pt;
}
grid[v] = line;
}
return(grid);
}
/*----------------------------------------------------------------------*
Execute the warp
*----------------------------------------------------------------------*/
Transform.prototype.warpExec = function() {
var h,v;
var line, pt;
//-------- IDs
var idTrnf = charIDToTypeID( "Trnf" );
var idnull = charIDToTypeID( "null" );
var idOfst = charIDToTypeID( "Ofst" );
var idHrzn = charIDToTypeID( "Hrzn" );
var idRlt = charIDToTypeID( "#Rlt" );
var idVrtc = charIDToTypeID( "Vrtc" );
var idwarp = stringIDToTypeID( "warp" );
var idPxl = charIDToTypeID( "#Pxl" );
var idrationalPoint = stringIDToTypeID( "rationalPoint" );
var idwarp = stringIDToTypeID( "warp" );
var idwarpStyle = stringIDToTypeID( "warpStyle" );
var idwarpCustom = stringIDToTypeID( "warpCustom" );
var idwarpValue = stringIDToTypeID( "warpValue" );
var idwarpPerspective = stringIDToTypeID( "warpPerspective" );
var idwarpPerspectiveOther = stringIDToTypeID( "warpPerspectiveOther" );
var idwarpRotate = stringIDToTypeID( "warpRotate" );
var idOrnt = charIDToTypeID( "Ornt" );
var idbounds = stringIDToTypeID( "bounds" );
var idTop = charIDToTypeID( "Top " );
var idLeft = charIDToTypeID( "Left" );
var idBtom = charIDToTypeID( "Btom" );
var idRght = charIDToTypeID( "Rght" );
var idRctn = charIDToTypeID( "Rctn" );
var iduOrder = stringIDToTypeID( "uOrder" );
var idvOrder = stringIDToTypeID( "vOrder" );
var idcustomEnvelopeWarp = stringIDToTypeID( "customEnvelopeWarp" );
var idmeshPoints = stringIDToTypeID( "meshPoints" );
var idrationalPoint = stringIDToTypeID( "rationalPoint" );
var idPath = charIDToTypeID( "Path" );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
var idFTcs = charIDToTypeID( "FTcs" );
var idQCSt = charIDToTypeID( "QCSt" );
var idQcsa = charIDToTypeID( "Qcsa" );
//--------
var desc15 = new ActionDescriptor();
var ref8 = new ActionReference();
ref8.putEnumerated( idPath, idOrdn, idTrgt );
desc15.putReference( idnull, ref8 );
desc15.putEnumerated( idFTcs, idQCSt, idQcsa );
var desc16 = new ActionDescriptor();
desc16.putUnitDouble( idHrzn, idRlt, 0.000000 );
desc16.putUnitDouble( idVrtc, idRlt, 0.000000 );
desc15.putObject( idOfst, idOfst, desc16 );
var desc17 = new ActionDescriptor();
desc17.putEnumerated( idwarpStyle, idwarpStyle, idwarpCustom );
desc17.putDouble( idwarpValue, 0.00000 );
desc17.putDouble( idwarpPerspective, 0.000000 );
desc17.putDouble( idwarpPerspectiveOther, 0.000000 );
desc17.putEnumerated( idwarpRotate, idOrnt, idHrzn );
// Layer.bounds
var desc18 = new ActionDescriptor();
desc18.putUnitDouble( idTop, idPxl, this.top); // [1] TopLeftY
desc18.putUnitDouble( idLeft, idPxl, this.left); // [0] TopLeftx
desc18.putUnitDouble( idBtom, idPxl, this.bottom); // [3] BottomRightY
desc18.putUnitDouble( idRght, idPxl, this.right); // [2] BottomRightX
desc17.putObject( idbounds, idRctn, desc18 );
desc17.putInteger( iduOrder, this.gridHNb+1 );
desc17.putInteger( idvOrder, this.gridVNb+1 );
// List of mesh points
// 0 1 2 3
// 4 5 6 7
// ...
var list1 = new ActionList();
for (v = 0;v <= this.gridVNb;v++) {
line = this.grid[v];
for (h = 0;h <= this.gridHNb;h++) {
pt = line[h];
var descPt = new ActionDescriptor();
try{
descPt.putUnitDouble( idHrzn, idPxl, pt[0]);
descPt.putUnitDouble( idVrtc, idPxl, pt[1]);
} catch(ex) {
alert("==>" + ex.message + "<" + pt[0] + "," + pt[1] + ">");
}
list1.putObject( idrationalPoint, descPt );
}
}
var desc19 = new ActionDescriptor();
desc19.putList( idmeshPoints, list1 );
desc17.putObject( idcustomEnvelopeWarp, idcustomEnvelopeWarp, desc19 );
desc15.putObject( idwarp, idwarp, desc17 );
executeAction( idTrnf, desc15, DialogModes.NO );
}
/************************************************************************
Fan
************************************************************************
Build a fan with a wide image.
************************************************************************
13/03/2010 V1r02b Habaki: Improve dialog
12/03/2010 V1r02 Habaki: Add the fan angle
09/03/2010 V1r01 Habaki: Creation
************************************************************************/
//#include "transfrm.jsx"
//#include "paths.jsx"
/*----------------------------------------------------------------------*
Constructor
*----------------------------------------------------------------------*/
function Fan()
{
this.FLName="Fan"; // Result layer name
this.LRef = app.activeDocument.activeLayer; // Reference layer
this.FPNb = 7; // Number of pieces
this.FRadius = 150; // Radius of circle: % of image height (> 100)
this.FCorner = 6.0; // % of image height
this.FMargin = 2.0; // % of image height
this.FAngle = 180; // Angle of the fan (0..360dgs)
}
/*----------------------------------------------------------------------*
Dialog
*----------------------------------------------------------------------*/
Fan.prototype.dialogBuild = function(
)
{
var Coord = this.LRef.bounds;
var LHeight = Coord[3] - Coord[1];
var LWidth = Coord[2] - Coord[0];
var myResource = "dialog{ \
orientation: 'column',\
alignChildren: 'fill',\
panelLayer: Panel {\
orientation: 'column',\
alignChildren: 'fill',\
text: 'IMAGE to Process',\
Name: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Layer: '},\
Val: StaticText {text: ''}\
},\
Height: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Height: ', characters: 25},\
Val: StaticText {text: '?'}\
},\
Width: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Width', characters: 25},\
Val: StaticText {text: '?'}\
}\
},\
panelCircle: Panel {\
orientation: 'column',\
alignChildren: 'fill',\
text: 'FAN',\
Nb: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Nb of pieces:', characters: 25},\
Val: EditText {text: '7', bounds: [0,0,40,20]}\
},\
Angle: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Fan angle (dgs):', characters: 25},\
Val: EditText { text: '180', bounds: [0,0,40,20]}\
},\
s00: StaticText {text: 'All % are relative to image Height.', characters: 35 },\
Rad: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Circle radius (% > 100):', characters: 25},\
Val: EditText { text: '150', bounds: [0,0,40,20]},\
},\
Corn: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Corner radius (%):', characters: 25 },\
Val: EditText { text: '6', bounds: [0,0,40,20]},\
},\
Marg: Group {\
orientation: 'row',\
Msg: StaticText {text: 'Margin between pieces (%):' , characters: 25},\
Val: EditText { text: '2', bounds: [0,0,40,20] }\
}\
},\
sign: StaticText {text: 'Habaki (c) 2010', characters: 15 },\
groupButtons: Group {\
orientation: 'row',\
buttonOk: Button { text: 'Build', enabled: 'false'},\
buttonCancel: Button { text: 'Cancel'},\
}\
};"
var myDialog = new Window(myResource, " Build a FAN");
myDialog.fanInstance = this;
myDialog.panelLayer.Name.Val.text = this.LRef.name;
myDialog.panelLayer.Height.Val.text = LHeight;
myDialog.panelLayer.Width.Val.text = LWidth;
myDialog.panelCircle.Angle.Val.text = "" + this.FAngle;
myDialog.panelCircle.Rad.Val.text = "" + this.FRadius;
myDialog.panelCircle.Corn.Val.text = "" + this.FCorner;
myDialog.panelCircle.Marg.Val.text = "" + this.FMargin*2;
// determines return value and keyboard shortcuts
myDialog.defaultElement = myDialog.groupButtons.buttonOk; // == return value 1
myDialog.cancelElement = myDialog.groupButtons.buttonCancel;
/*--------------------------------------------------*/
return myDialog
}
/*----------------------------------------------------------------------*
Get parameters from User
*----------------------------------------------------------------------*/
Fan.prototype.getParams = function()
{
var Doc = app.activeDocument;
var LNb;
var i;
var LL;
var myDialog = this.dialogBuild();
var OK = (myDialog.show() == 1) ? true : false;
if (OK) {
this.FPNb = parseInt(myDialog.panelCircle.Nb.Val.text);
this.FAngle = parseFloat(myDialog.panelCircle.Angle.Val.text);
this.FRadius = parseFloat(myDialog.panelCircle.Rad.Val.text);
this.FCorner = parseFloat(myDialog.panelCircle.Corn.Val.text);
this.FMargin = parseFloat(myDialog.panelCircle.Marg.Val.text)/2;
}
return (OK);
}
/*----------------------------------------------------------------------*
Build process
*----------------------------------------------------------------------*/
Fan.prototype.build = function()
{
try{
// Config parameters
var LRef = this.LRef; // Reference layer
// Computed values
var FRadE; // External radius
var FRadI; // Internal radius
var LWE, LWI; // External,Internal widths for a piece
var FCntX, FCntY; // Fan center
var LAngl0; // Angle of a piece
var LHeight, LWidth, LTX, LTY, LW;
var FL, LL; // layers
var Coord;
var i;
var pathItem;
var FMarg, FCorn;
var PI = 3.14159265;
var Doc = app.activeDocument;
var Sel = Doc.selection;
// Fan position (Center):
FCntX = Doc.width /2; // Horizontal
FCntY = Doc.height*4/5; // Vertical
// Dimension of the image
// Coord: TopLeftX,TopLeftY,BottomRightX,BottomRightY
Coord = LRef.bounds;
LHeight = Coord[3] - Coord[1];
LWidth = Coord[2] - Coord[0];
if (this.FRadius < 100) this.FRadius = 100;
FRadE = LHeight*this.FRadius/100; // External radius
FRadI = FRadE - LHeight; // Internal radius
LWE = FRadE * 2 * Math.sin(PI*this.FAngle / (this.FPNb * 360));
LWI = LWE * FRadI / FRadE;
FMarg = LHeight * this.FMargin / 100;
FCorn = LHeight * this.FCorner / 100;
var Tf = new Transform();
Tf.grid = Tf.gridTrapez(LWI * 100.0 / LWE);
// Result layer
FL = Doc.artLayers.add();
FL.name = this.FLName;
// Split image in pieces
LW = LWidth/ this.FPNb;
LTX = Coord[0];
LTY = Coord[1];
LAngl0 = PI*this.FAngle / (this.FPNb*180);
for (i=0; i < this.FPNb; i++) {
//alert("W:" + LW + ", X:" + LTX + ", Y:" + LTY);
// Make a path with rounder corners to cut a piece
PathRect(LTY, LTX+FMarg, LTY+LHeight, LTX+LW-FMarg, FCorn);
pathItem = Doc.pathItems[0];
pathItem.makeSelection();
pathItem.remove();
// Cut a piece
LL = LRef.duplicate();
Doc.activeLayer = LL;
Sel.invert();
Sel.clear();
Sel.deselect();
// Scale
LL.resize(LWE*100 / LW,
(FRadE - FRadI) * 100 / LHeight,
AnchorPosition.TOPCENTER);
// Trapezium
Tf.warpExec();
// Move TopCenter
LL.translate(FCntX - LTX -LW/2
+ FRadE*(Math.cos(PI - LAngl0*(i)) +
Math.cos(PI - LAngl0*(i+1))
)/2
,
FCntY - LTY
- FRadE*(Math.sin(PI - LAngl0*(i)) +
Math.sin(PI - LAngl0*(i+1))
)/2
);
// Rotate
LL.rotate(this.FAngle*(i + 0.5)/this.FPNb -90, AnchorPosition.TOPCENTER);
// Merge with the result
LL.move(FL, ElementPlacement.PLACEBEFORE);
LL.merge();
// Another piece
LTX += LW;
} // for
} catch(ex){
alert(ex.message);
}
}
/*----------------------------------------------------------------------*
Execution
*----------------------------------------------------------------------*/
var RulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var fan = new Fan();
if (fan.getParams())
fan.build();
app.preferences.rulerUnits = RulerUnits;
/*----------------------------------------------------------------------*/