Stdlib.getVectorMaskBounds + path partially outside canvas

Discussion of the xtools Toolkit

Moderators: Tom, Kukurykus

SzopeN

Stdlib.getVectorMaskBounds + path partially outside canvas

Post by SzopeN »

Using Stdlib.getVectorMaskBounds one could only get bounding rect clipped to canvas size, because making selection outside canvas is impossible. Sometimes it is necessary to get real, not clipped bounds. If paths on vector mask consist only of corner points or only corner points determines clipping rect of a path (i.e. no curve stick out of it) one can use the following function to get paths clipping rect NOT clipped to canvas size:
Code: Select all
// by Damian SzopeN Sepczuk <damian[d0t]sepczuk[a7]o2{do7}pl>
// [in] round (bool) -- whether returned values should be rounded to the nearest pixel, def: false
// [in] doc -- document containing layer with vector mask
// [in] layer -- layer with vector mask
// returns array [left, top, right, bottom, width, height]
Stdlib.getVectorMaskBounds_cornerPointsOnly = function(round, doc, layer) {
  round = !!round;
  function _ftn() {
    var ref = new ActionReference();
    ref.putEnumerated( cTID('Path'), cTID('Path'), sTID('vectorMask') );
    ref.putEnumerated(cTID("Lyr "), cTID("Ordn"), cTID("Trgt"));
    var vMaskDescr = executeActionGet(ref);
    var pathContents = vMaskDescr.getObjectValue(sTID('pathContents'));
    var pathList = pathContents.getList(sTID('pathComponents'));

    // for each path in current layer
    var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
    for ( cPath=0; cPath<pathList.count; ++cPath )
    {
      var curPath = pathList.getObjectValue(cPath).getList(sTID("subpathListKey"));
      var points = curPath.getObjectValue(0).getList(sTID("points"));
      // for each point
      for (cPoint=0; cPoint < points.count; ++cPoint )
      {   
        var point = points.getObjectValue(cPoint).getObjectValue(sTID("anchor"));
        var x = point.getUnitDoubleValue(sTID('horizontal'));
        var y = point.getUnitDoubleValue(sTID('vertical'));
        if ( x < minX ) minX = x; // it is faster than if/else block (benchmarked on PSCS4)
        if ( x > maxX ) maxX = x;
        if ( y < minY ) minY = y;
        if ( y > maxY ) maxY = y;
      }
    }
    res = [minX, minY, maxX, maxY, maxX-minX, maxY-minY];
    if (round)
    {
      for ( i=0; i<res.length; ++i )
      {
        res = Math.round(res);
      }
    }
    return res;
  }
  var bnds = Stdlib.wrapLCLayer(doc, layer, _ftn);
  return bnds;
}

Function Stdlib.wrapLCLayer is in stdlib.js file.
xbytor

Stdlib.getVectorMaskBounds + path partially outside canvas

Post by xbytor »

Nice clean code. If you want to get a little bit more out of it, try moving the sTID calls outside of the loops (via additional variables). It may or may not make a noticeable difference, but if your benchmarking noticed a difference for if/else-if it's probably worth checking into.

BTW, do you want/mind me folding your stuff into stdlib.js (with attribution, of course)?

-X
SzopeN

Stdlib.getVectorMaskBounds + path partially outside canvas

Post by SzopeN »

BTW, do you want/mind me folding your stuff into stdlib.js (with attribution, of course)?
If you think it's a good idea -- go ahead

If you want to get a little bit more out of it, try moving the sTID calls outside of the loops (via additional variables).
Good suggestion I've benchmarked the code w/o using xTID (bare numerical values), gaining ca. 33% but forgot to optimize the loop itself :stupid:. It could be also a good idea to buffer once checked IDs in JS array in xTID function (dynamic programming). Scripts heavily using xTID (for example in loops) could benefit from it. I'll test it today.

Updated code below:
Code: Select all// by Damian SzopeN Sepczuk <damian[d0t]sepczuk[a7]o2{do7}pl>
// [in] round (bool) -- whether returned values should be rounded to the nearest pixel, def: false
// [in] doc -- document containing layer with vector mask
// [in] layer -- layer with vector mask
// returns array [left, top, right, bottom, width, height]
Stdlib.getVectorMaskBounds_cornerPointsOnly = function(round, doc, layer) {
  round = !!round;
  function _ftn() {
    var ref = new ActionReference();
    ref.putEnumerated( cTID('Path'), cTID('Path'), sTID('vectorMask') );
    ref.putEnumerated(cTID("Lyr "), cTID("Ordn"), cTID("Trgt"));
    var vMaskDescr = executeActionGet(ref);
    var pathContents = vMaskDescr.getObjectValue(sTID('pathContents'));
    var pathList = pathContents.getList(sTID('pathComponents'));

    // for each path in current layer
    var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
    // using separate variables gives speed gain
    var _id1 = sTID("subpathListKey"),
        _id2 = sTID("points"),
        _id3 = sTID("anchor"),
        _id4 = sTID('horizontal'),
        _id5 = sTID('vertical');
      
    for ( cPath=0; cPath<pathList.count; ++cPath )
    {
      var curPath = pathList.getObjectValue(cPath).getList(_id1);
      var points = curPath.getObjectValue(0).getList(_id2);
      // for each point
      for (cPoint=0; cPoint < points.count; ++cPoint )
      {   
        var point = points.getObjectValue(cPoint).getObjectValue(_id3);
        var x = point.getUnitDoubleValue(_id4);
        var y = point.getUnitDoubleValue(_id5);
        if ( x < minX ) minX = x; // it is faster than if/else block (benchmarked on PSCS4)
        if ( x > maxX ) maxX = x;
        if ( y < minY ) minY = y;
        if ( y > maxY ) maxY = y;
      }
    }
    res = [minX, minY, maxX, maxY, maxX-minX, maxY-minY];
    if (round)
    {
      for ( i=0; i<res.length; ++i )
      {
        res = Math.round(res);
      }
    }
    return res;
  }
  var bnds = Stdlib.wrapLCLayer(doc, layer, _ftn);
  return bnds;
}


-- update
I've tested buffered versions of xTID. The conclusion is -- they (generally) do no harm. Code and tests (CS4) below:
Code: Select allcTID_global_array = new Array();
function cTID(s) { return cTID_global_array[s] || cTID_global_array[s]=app.charIDToTypeID(s); };
sTID_global_array = new Array();
function sTID(s) { return sTID_global_array[s] || sTID_global_array[s]=app.stringIDToTypeID(s); };


Test results:
Code: Select alldifferent variations of getVectorMaskBounds_cornerPointsOnly (10000 invokes, layer mask with 3 paths each containing 10 corner points)
===============================================
Version Infinity:                 (11921003+11902229+11907602)/3=11910278
Version w/o xTID:                 (7033363+6994813+7054897)/3   = 7027691
Version w/ xTID before loop var:   (7600072+7580678+7547901)/3  = 7576217
Version w/ xTID before loop arrNum:(7709093+7725437+7697937)/3  = 7710822
Version w/ xTID before loop arrStr:(7798030+7776608+7772954)/3  = 7782531
Version w/ xTID before loop Obj:   (7782374+7793718+7775850)/3  = 7783980
Version Inf w/ buffered xTID:   (10440350+10377560+10423510)/3  =10413807
Version w/ buf xTID and opt loop var:(7560101+7567488+7582057)/3= 7569882


Code: Select allBuffered xTID itself
===============================================
Test code

var iter=NUM_OF_ITERATIONS;
for ( var i=0; i<iter; ++i ) {
   sTID('vectorMask');
   sTID('pathContents');
   sTID('subpathListKey');
   sTID('anchor');
}

NoOfIter    w/o buffering   w/ buffering    time diff   % speedup
      1          32              40              -8     -25%
      2          40              45              -5     -13%
      5          66              58               8      12%
     10         111              82              29      26%
    100         895             445             450      50%
   1000       10255            4298            5957      58%
  10000      105075           42208           62867      59%
 100000      944331          426956          517375      55%
1000000     8994127         4125196         4868931      54%
xbytor

Stdlib.getVectorMaskBounds + path partially outside canvas

Post by xbytor »

I've added Stdlib.getVectorMaskBounds_cornerPointsOnly (with a couple of 'var's stuck in).

I've also added ID functions as _cTID and _sTID with minor changes to re-scope (and rename) the id cache.

{Edit} I will probably convert cTID and sTID over at a later time after I've done more testing with the cache versions.

These will be in the push of stdlib.js.

-X
SzopeN

Stdlib.getVectorMaskBounds + path partially outside canvas

Post by SzopeN »

I've added Stdlib.getVectorMaskBounds_cornerPointsOnly (with a couple of 'var's stuck in).
I think, you are speaking of (_id1, ..., _id5) fragment? Isn't the snippet
Code: Select allvar a=2,
    b=3,
    c=4;

equal to
Code: Select allvar a=2;
var b=3;
var c=4;

Either way, all variables should be in local scop.

I will probably convert cTID and sTID over at a later time after I've done more testing with the cache versions.
I've tested several ways of returning-or-computing cached value (using if, ?:, typeof) but || version turned out to be the best one. Please share your ideas and tests itself when you're done.
xbytor

Stdlib.getVectorMaskBounds + path partially outside canvas

Post by xbytor »

Either way, all variables should be in local scop.

cPoint, cPath, and i were the ones that needed the 'var'.


but || version turned out to be the best one.

I'm not surprised. I use the idiom self, a habit I picked up in my perl days.

I'll post something here when I make the switch.

-X