simple distortion defined by 4 corners / points

Upload Photoshop Scripts, download Photoshop Scripts, Discussion and Support of Photoshop Scripts

Moderators: Tom, Kukurykus

müller

simple distortion defined by 4 corners / points

Post by müller »

Hi there

For all of those who had a little hassle trying to find out how photoshop performs its 'Free Transform', I tinkered a little script that does this transformation by using the envelope warp action.

The envelope warp basically works with a grid defined by 16 meshpoints. To calculate the horizontal and vertical positions of that points in accordance to a perspective, the script calculates the vanishing points of both pairs of opposite sides (of that 'surface' defined by our 4 points).
The vanishing points help to divide each side perspectively correct by simply connecting the intersection of the surface-diagonals with the vanishing points, doing the same on the resulting 4 sub-surfaces and so forth, whereby any point on that surface can be found and projected to our mere 2-dimensional photoshop document. This approximation is used to find the other 12 meshpoints.

The script is at preliminary state.

Comments, suggestions and ideas for improvement are welcome.


Now here's the script:

Code: Select all
// the perspective-grid-class to project points

var PGrid = function ( p1, p2, p3, p4, parentGrid )
{
   var intersect = function ( p1, p2, p3, p4 )
   {
      var x1 = p1[0], y1 = p1[1], x2 = p2[0], y2 = p2[1], x3 = p3[0], y3 = p3[1], x4 = p4[0], y4 = p4[1];
      var q = ((x4-x3)*(y2-y1)-(x2-x1)*(y4-y3));
      if (q==0) return false;
      var s = ((x2-x1)*(y3-y1)-(y2-y1)*(x3-x1))/q;
      return [x3+s*(x4-x3), y3+s*(y4-y3)];
   }
   
   if (!parentGrid)
   {
      this.vph = intersect ( p1, p2, p3, p4 ) || [1e24, (p1[1]+(p4[1]-p1[1])/2)];
      this.vpv = intersect ( p1, p4, p2, p3 ) || [(p1[0]+(p2[0]-p1[0])/2), 1e24];
   }
   else
   {
      this.vph = parentGrid.vph;
      this.vpv = parentGrid.vpv;
   }
   
   this.cntr = intersect ( p1, p3, p2, p4 );
   this.bounds = [ p1, p2, p3, p4 ];
   
   var mp1 = intersect( this.vpv, this.cntr, p1, p2 );
   var mp2 = intersect( this.vph, this.cntr, p2, p3 );
   var mp3 = intersect( this.vpv, this.cntr, p3, p4 );
   var mp4 = intersect( this.vph, this.cntr, p4, p1 );
   
   this.mids = [ mp1, mp2, mp3, mp4 ];
   
   this.releaseChild = function (q)
   {
      var nPGrid;
      switch (q)
      {
         case 1:nPGrid = new PGrid( p1, this.mids[0], this.cntr, this.mids[3], this );break;
         case 3:nPGrid = new PGrid( this.cntr, this.mids[1], p3, this.mids[2], this );break;
         default:nPGrid = new PGrid( this.mids[3], this.cntr, this.mids[2], p4, this );break;
      }
      return nPGrid;
   }
   
   this.project = function (pt)
   {
      var tolerance = 1/Math.pow(2, 24);
      var approx, val, sval, tkey, child;
      var arr = [];
      var key = [[3,4],[1,4]];
      
      var roundByTolerance = function (val) { return ( val-=(val%tolerance) ); }

      if (pt[0]==1) pt[0]-=tolerance;
      if (pt[1]==1) pt[1]-=tolerance;
      
      pt[0] = roundByTolerance(pt[0]);
      pt[1] = roundByTolerance(pt[1]);
      
      for ( var n = 0; n<pt.length; n++ )
      {
         val = pt[n], sval = 1, tkey = key[n], approx = 0, child = undefined;         
         while( approx-val!=0 )
         {
            while ((approx+sval)>val)  child = (child||this).releaseChild( ((approx+(sval/=2))<=val)?(tkey[0]):(tkey[1]) );
            approx+=sval;
         }
         
         arr.push(child||this);
      }
      return intersect( arr[0].bounds[0], arr[0].bounds[3], arr[1].bounds[3], arr[1].bounds[2] );
   }
   
   this.sortPoints = function (arr)
   {
      var sortfx = function (ar,i) { ar.sort(function (a,b){return a>b;}); }
      sortfx(arr, 1);
      var arr1 = arr.slice(0,2),arr2 = arr.slice(2,4);
      sortfx(arr1,0);sortfx(arr2,0);
      return [arr1[0], arr1[1], arr2[1], arr2[0]];
   }
}

function distortSimple (p1, p2, p3, p4)
{
   var ctid = charIDToTypeID;
   var stid = stringIDToTypeID;
   
   var bounds = activeDocument.activeLayer.bounds;
   var pgrid = new PGrid(p4,p3,p2,p1);   

   var desc01 = new ActionDescriptor();
   var ref01 = new ActionReference();
   ref01.putEnumerated(ctid("Lyr "), ctid("Ordn"), ctid("Trgt"));
   desc01.putReference(ctid("null"), ref01);
   desc01.putEnumerated(ctid("FTcs"), ctid("QCSt"), ctid("Qcsa"));
   var desc02 = new ActionDescriptor();
   desc02.putUnitDouble(ctid("Hrzn"), ctid("#Pxl"), 0.000000);
   desc02.putUnitDouble(ctid("Vrtc"), ctid("#Pxl"), 0.000000);
   desc01.putObject(ctid("Ofst"), ctid("Ofst"), desc02);

   var desc03 = new ActionDescriptor();
   desc03.putEnumerated(stid("warpStyle"), stid("warpStyle"), stid("warpCustom"));
   desc03.putDouble(stid("warpValue"), 0.000000);
   desc03.putDouble(stid("warpPerspective"), 0.000000);
   desc03.putDouble(stid("warpPerspectiveOther"), 0.000000);
   desc03.putEnumerated(stid("warpRotate"), ctid("Ornt"), ctid("Hrzn"));

   var desc04 = new ActionDescriptor();
   desc04.putUnitDouble(ctid("Top "), ctid("#Pxl"), bounds[0]);           
   desc04.putUnitDouble(ctid("Left"), ctid("#Pxl"), bounds[1]);                     
   desc04.putUnitDouble(ctid("Btom"), ctid("#Pxl"), bounds[2]);     
   desc04.putUnitDouble(ctid("Rght"), ctid("#Pxl"), bounds[3]);

   desc03.putObject(stid("bounds"), ctid("Rctn"), desc04);
   desc03.putInteger(stid("uOrder"), 4);
   desc03.putInteger(stid("vOrder"), 4);

   var desc05 = new ActionDescriptor();
   var mpList = new ActionList();

   for (var n=0;n<16;n++)
   {   
      var mp = pgrid.project([(n%4)/3,Math.floor(n/4)/3]);
      var desc = new ActionDescriptor();
      desc.putUnitDouble(ctid("Hrzn"), ctid("#Pxl"), mp[0]);
      desc.putUnitDouble(ctid("Vrtc"), ctid("#Pxl"), mp[1]);
      mpList.putObject(stid("rationalPoint"), desc);
   }

   desc05.putList(stid("meshPoints"), mpList);
   desc03.putObject(stid("customEnvelopeWarp"), stid("customEnvelopeWarp"), desc05);
   
   desc01.putObject(stid("warp"), stid("warp"), desc03);
   executeAction(ctid("Trnf"), desc01, DialogModes.NO);
}

//
// EXAMPLE
//

// array with 4 points in the order:
// topleft, topright, bottomright, bottomleft

var arr = [[350,350],[680,300],[650,640],[350,650]];

distortSimple(arr[0],arr[1],arr[2],arr[3]);



Torsion doesn't work yet. Ensure that the 4 points you define are on a 'planar' surface.

ml
Thomas
müller

simple distortion defined by 4 corners / points

Post by müller »

There was small mistake dealing with the layer-bounds.

Fixed:

Code: Select allvar PGrid = function ( p1, p2, p3, p4, parentGrid )
{
   var intersect = function ( p1, p2, p3, p4 )
   {
      var x1 = p1[0], y1 = p1[1], x2 = p2[0], y2 = p2[1], x3 = p3[0], y3 = p3[1], x4 = p4[0], y4 = p4[1];
      var q = ((x4-x3)*(y2-y1)-(x2-x1)*(y4-y3));
      if (q==0) return false;
      var s = ((x2-x1)*(y3-y1)-(y2-y1)*(x3-x1))/q;
      return [x3+s*(x4-x3), y3+s*(y4-y3)];
   }
   
   if (!parentGrid)
   {
      this.vph = intersect ( p1, p2, p3, p4 ) || [1e24, (p1[1]+(p4[1]-p1[1])/2)];
      this.vpv = intersect ( p1, p4, p2, p3 ) || [(p1[0]+(p2[0]-p1[0])/2), 1e24];
   }
   else
   {
      this.vph = parentGrid.vph;
      this.vpv = parentGrid.vpv;
   }
   
   this.cntr = intersect ( p1, p3, p2, p4 );
   this.bounds = [ p1, p2, p3, p4 ];
   
   var mp1 = intersect( this.vpv, this.cntr, p1, p2 );
   var mp2 = intersect( this.vph, this.cntr, p2, p3 );
   var mp3 = intersect( this.vpv, this.cntr, p3, p4 );
   var mp4 = intersect( this.vph, this.cntr, p4, p1 );
   
   this.mids = [ mp1, mp2, mp3, mp4 ];
   
   this.releaseChild = function (q)
   {
      var nPGrid;
      switch (q)
      {
         case 1:nPGrid = new PGrid( p1, this.mids[0], this.cntr, this.mids[3], this );break;
         case 3:nPGrid = new PGrid( this.cntr, this.mids[1], p3, this.mids[2], this );break;
         default:nPGrid = new PGrid( this.mids[3], this.cntr, this.mids[2], p4, this );break;
      }
      return nPGrid;
   }
   
   this.project = function (pt)
   {
      var tolerance = 1/Math.pow(2, 24);
      var approx, val, sval, tkey, child;
      var arr = [];
      var key = [[3,4],[1,4]];
      
      var roundByTolerance = function (val) { return ( val-=(val%tolerance) ); }

      if (pt[0]==1) pt[0]-=tolerance;
      if (pt[1]==1) pt[1]-=tolerance;
      
      pt[0] = roundByTolerance(pt[0]);
      pt[1] = roundByTolerance(pt[1]);
      
      for ( var n = 0; n<pt.length; n++ )
      {
         val = pt[n], sval = 1, tkey = key[n], approx = 0, child = undefined;         
         while( approx-val!=0 )
         {
            while ((approx+sval)>val)  child = (child||this).releaseChild( ((approx+(sval/=2))<=val)?(tkey[0]):(tkey[1]) );
            approx+=sval;
         }
         
         arr.push(child||this);
      }
      return intersect( arr[0].bounds[0], arr[0].bounds[3], arr[1].bounds[3], arr[1].bounds[2] );
   }
   
   this.sortPoints = function (arr)
   {
      var sortfx = function (ar,i) { ar.sort(function (a,b){return a>b;}); }
      sortfx(arr, 1);
      var arr1 = arr.slice(0,2),arr2 = arr.slice(2,4);
      sortfx(arr1,0);sortfx(arr2,0);
      return [arr1[0], arr1[1], arr2[1], arr2[0]];
   }
}

function distortSimple (p1, p2, p3, p4)
{
   var ctid = charIDToTypeID;
   var stid = stringIDToTypeID;
   
   var bounds = activeDocument.activeLayer.bounds;
   var pgrid = new PGrid(p4,p3,p2,p1);   

   var desc01 = new ActionDescriptor();
   var ref01 = new ActionReference();
   ref01.putEnumerated(ctid("Lyr "), ctid("Ordn"), ctid("Trgt"));
   desc01.putReference(ctid("null"), ref01);
   desc01.putEnumerated(ctid("FTcs"), ctid("QCSt"), ctid("Qcsa"));
   var desc02 = new ActionDescriptor();
   desc02.putUnitDouble(ctid("Hrzn"), ctid("#Pxl"), 0.000000);
   desc02.putUnitDouble(ctid("Vrtc"), ctid("#Pxl"), 0.000000);
   desc01.putObject(ctid("Ofst"), ctid("Ofst"), desc02);

   var desc03 = new ActionDescriptor();
   desc03.putEnumerated(stid("warpStyle"), stid("warpStyle"), stid("warpCustom"));
   desc03.putDouble(stid("warpValue"), 0.000000);
   desc03.putDouble(stid("warpPerspective"), 0.000000);
   desc03.putDouble(stid("warpPerspectiveOther"), 0.000000);
   desc03.putEnumerated(stid("warpRotate"), ctid("Ornt"), ctid("Hrzn"));

   var desc04 = new ActionDescriptor();
   desc04.putUnitDouble(ctid("Top "), ctid("#Pxl"), bounds[1]);           
   desc04.putUnitDouble(ctid("Left"), ctid("#Pxl"), bounds[0]);                     
   desc04.putUnitDouble(ctid("Btom"), ctid("#Pxl"), bounds[3]);     
   desc04.putUnitDouble(ctid("Rght"), ctid("#Pxl"), bounds[2]);

   desc03.putObject(stid("bounds"), ctid("Rctn"), desc04);
   desc03.putInteger(stid("uOrder"), 4);
   desc03.putInteger(stid("vOrder"), 4);

   var desc05 = new ActionDescriptor();
   var mpList = new ActionList();

   for (var n=0;n<16;n++)
   {   
      var mp = pgrid.project([(n%4)/3,Math.floor(n/4)/3]);
      var desc = new ActionDescriptor();
      desc.putUnitDouble(ctid("Hrzn"), ctid("#Pxl"), mp[0]);
      desc.putUnitDouble(ctid("Vrtc"), ctid("#Pxl"), mp[1]);
      mpList.putObject(stid("rationalPoint"), desc);
   }

   desc05.putList(stid("meshPoints"), mpList);
   desc03.putObject(stid("customEnvelopeWarp"), stid("customEnvelopeWarp"), desc05);
   
   desc01.putObject(stid("warp"), stid("warp"), desc03);
   executeAction(ctid("Trnf"), desc01, DialogModes.NO);
}

// EXAMPLE AS MENTIONED IN MY FIRST POST

photoguy

simple distortion defined by 4 corners / points

Post by photoguy »

I tried this script as is and am getting error
Code: Select allfunction distortSimple (p1, p2, p3, p4)
{
   var ctid = charIDToTypeID;
   var stid = stringIDToTypeID;




the error is: charIDToTypeID is undefined
Mike Hale

simple distortion defined by 4 corners / points

Post by Mike Hale »

If you are getting that error I'll bet that you are running the script in ExtendScript toolkit without setting the target to Photoshop.