Get details about guides

Photoshop Script Snippets - Note: Full Scripts go in the Photoshop Scripts Forum

Moderators: Tom, Kukurykus

Mike Hale

Get details about guides

Post by Mike Hale »

I don't know how helpful this will be but it does seem to work. Any help on making this more useful would be great.

Code: Select all/* the info about guides can be found in the photoshop tag 35 42 49 4D 04 08
   the length of the tag is found at offset 11. that value is the length to the next tag
   the number of guides can be found at offset 27. From that point the data repeats
   for each guide. 4 bytes for the location. That value / 32 = pixels
   and one byte for type 0 = vert and 1 = horz
   Note: this info is just a best guess from looking at the data
*/


var guideTag = '8BIM' + fromCharCode( 4 ) + fromCharCode( 8 );
var f = new File('/c/temp4/guide.psd');
f.encoding = 'BINARY';
f.open('r');
var str = f.read();
f.close();
var tagPos = str.search(guideTag);
var tagLength = 12 + str.charCodeAt( tagPos + 11 );
var guideStr = str.substring( tagPos, tagPos+tagLength );
var guides = new Object;

guides.count = str.charCodeAt( tagPos + 27 );
alert( guides.count );
var pointer = tagPos + 28;
for( var i =0; i < guides.count; i++ ){
   var gType =  str.charCodeAt( pointer + 4 ) ? 'horz':'vert'
   alert('Guide #'+(i+1) +' type: ' + gType+'\nGuide pos: ' + (Number('0x'+str.charCodeAt( pointer ).toString(16)+str.charCodeAt( pointer+1).toString(16)+str.charCodeAt( pointer+2).toString(16)+str.charCodeAt( pointer+3).toString(16))/32));
   pointer = pointer + 5;
}

function fromCharCode(input) {
   output = eval("String.fromCharCode(" + input + ")");
   return output;
}
Mike Hale

Get details about guides

Post by Mike Hale »

Here is a cleaned up version. It now saves the doc as a jpg to speed thing up and returns an object with the guide info
Code: Select all/* the info about guides can be found in the photoshop tag hex 35 42 49 4D 04 08
   the length of the tag is found at offset 11. that value is the length to the next tag
   the number of guides can be found at offset 27. From that point the data repeats
   for each guide. 4 bytes for the location. That value / 32 = pixels
   and one byte for type 0 = vert and 1 = horz
   Note: this info is just a best guess from looking at the data
*/
///////////////////////////////////////////////////////////////////////////////
// Function: getGuideInfo
// Description: Saves the document as a temp jpg file
//                        and extracts the info about the guides
//                        in document if any
// Usage: var guides = getGuideInfo( activeDocument );
// Input: Document
// Return: Object: count property  = number of guides
//                              X property = array of type and pixel pos
//                                                     where X = guide number
///////////////////////////////////////////////////////////////////////////////
function getGuideInfo( doc ) {
   saveOptions = new JPEGSaveOptions();
   saveOptions.quality = 0;// we don't care about image quality in the temp file
   var tempFile = new File( Folder.temp + '/' +'guideTemp.jpg' );
   doc.saveAs( tempFile, saveOptions, true );
   var guideTag = '8BIM' + fromCharCode( 4 ) + fromCharCode( 8 );
   tempFile.encoding = 'BINARY';
   tempFile.open('r');
   var str = tempFile.read();
   tempFile.close();
   tempFile.remove();
   var tagPos = str.search(guideTag);
   var guides = new Object;
   if( tagPos != -1 ) {
      var tagLength = 12 + str.charCodeAt( tagPos + 11 );
      var guideStr = str.substring( tagPos, tagPos+tagLength );
      guides.count = str.charCodeAt( tagPos + 27 );
      var pointer = tagPos + 28;
      for( var i =0; i < guides.count; i++ ){
         var n = Number( '0x'+str.charCodeAt( pointer ).toString(16)+str.charCodeAt( pointer+1).toString(16)+str.charCodeAt( pointer+2).toString(16)+str.charCodeAt( pointer+3).toString(16))/32;
         guides[ i + 1 ] =  [ (str.charCodeAt( pointer + 4 ) ? 'horz':'vert'), n ];      
         pointer = pointer + 5;
      }
   }else{
      guides.count = 0;
   }
   function fromCharCode(input) {
      output = eval("String.fromCharCode(" + input + ")");
   return output;
   }
   return guides;
}

var g = getGuideInfo( activeDocument );
alert( g.count );
xbytor

Get details about guides

Post by xbytor »

Here's my first (untested) cut at full Guide support in xtools.

Note that the final version of this will include the ability to remove guides one at a time as well as to move them.

-X

Code: Select all//
// Guides
//
// $Id$
// Copyright: (c)2009, xbytor
// License: http://creativecommons.org/licenses/LGPL/2.1
// Contact: xbytor@gmail.com
//
//
GuideType = function(name, id) {
  this.name = name;
  this.id = id;

  this.toString = function() {
    return 'GuideType' + this.name.toUpperCase();
  };
};
GuideType.prototype.typename = "GuideType";

GuideType.VERTICAL = new GuideType('vertical', 'Vrtc');
GuideType.HORIZONTAL = new GuideType('horizontal', 'Hrzn');

Guides = function(doc) {
  var self = this;

  if (!doc || doc.typename != "Document") {
    Error.runtimeError(2, "doc");
  }

  self.parent = doc;
  self.length = 0;
  self._init(doc);
};
Guides.prototype.typename = "Guides";

//
// Load up the Guides object with info from the doc
//
Guides.prototype._init = function() {
  var self = this;
  var guides = Guides.getGuideInfo(doc);

  for (var i = 0; i < guides.count; i++) {
    var guide = guides[i+1];
    var orient;
    if (guide[0] == 'horz') {
      orient = GuideType.HORIZONTAL;

    } else if (guide[1] == 'vert') {
      orient = GuideType.VERTICAL;
    }

    var gide = new Guide(self, orient, guide[1]);
    self = gide;
    self.length++;
  }
};
Guides.prototype.reload = function() {
  var self = this;
  for (var i; i < self.length; i++) {
    delete self;
  }
  self.length = 0;
  self._init();
};

Guides.prototype.add = function(orientation, position) {
  var self = this;
  function cTID(s) { return app.charIDToTypeID(s); };

  // add error checking for self.doc

  if (app.activeDocument != self.doc) {
    app.activeDocument = self.doc;
  }

  var id = undefined;

  if (!(orientation instanceof GuideType)) {
    var orient = orientation.toString().toLowerCase();
    if (orient == 'vertical' || orient == 'vrtc') {
      orientation = GuideType.VERTICAL;
    }

    if (orient == 'horizontal' || orient == 'hrzn') {
      orientation = GuideType.HORIZONTAL;
    }
  }

  if (!(orientation instanceof GuideType)) {
    Error.runtimeError(2, "orientation");
  }

  var pos = toNumber(position);
  if (isNaN(pos)) {
    Error.runtimeError(2, "position");
  }

  var desc = new ActionDescriptor();
  var gdesc = new ActionDescriptor();
  gdesc.putUnitDouble(cTID("Pstn"), cTID("#Pxl"), pos);
  gdesc.putEnumerated(cTID("Ornt"), cTID("Ornt"), cTID(orientation.id));
  desc.putObject(cTID("Nw  "), cTID("Gd  "), gdesc);
  executeAction(cTID("Mk  "), desc, DialogModes.NO);

  var guide = new Guide(self, orientation, pos);
  self[self.length++] = guide;

  return guide;
};

Guides.prototype.removeAll = function() {
  var self = this;
  function cTID(s) { return app.charIDToTypeID(s); };

  if (app.activeDocument != self.doc) {
    app.activeDocument = self.doc;
  }

  var desc = new ActionDescriptor();
  var ref = new ActionReference();
  ref.putEnumerated(cTID("Gd  "), cTID("Ordn"), cTID("Al  "));
  desc.putReference(cTID("null"), ref );
  executeAction(cTID("Dlt "), desc, DialogModes.NO);

  for (var i; i < self.length; i++) {
    delete self;
  }

  self.length = 0;

  return true;
};

Guide.prototype._removeGuide = function(guide) {
  var self = this;
  //XXX this can be implemented by reloading the guides
  // for self.doc, removeAll, then add all of the guides
  // back except for the one passed in as an argument
};


//
// From Mike Hale (http://ps-scripts.com/bb/viewtopic.php?p=12299#12299)
//
/* the info about guides can be found in the photoshop tag hex 35 42 49 4D 04 08
   the length of the tag is found at offset 11. that value is the length to the next tag
   the number of guides can be found at offset 27. From that point the data repeats
   for each guide. 4 bytes for the location. That value / 32 = pixels
   and one byte for type 0 = vert and 1 = horz
   Note: this info is just a best guess from looking at the data
*/
///////////////////////////////////////////////////////////////////////////////
// Function: getGuideInfo
// Description: Saves the document as a temp jpg file
//                        and extracts the info about the guides
//                        in document if any
// Usage: var guides = getGuideInfo( activeDocument );
// Input: Document
// Return: Object: count property  = number of guides
//                              X property = array of type and pixel pos
//                                                     where X = guide number
///////////////////////////////////////////////////////////////////////////////
Guides.getGuideInfo = function(doc) {
  var saveOptions = new JPEGSaveOptions();

  saveOptions.quality = 0; // we don't care about image quality in the temp file
  var tempFile = new File(Folder.temp + '/' + File().name + '.jpg');

  // Dupe the doc, make it savable as JPEG, save it, then close the file
  doc = doc.duplicate();
  doc.bitsPerChannel = BitsPerChannelType.EIGHT;
  doc.changeMode(ChangeMode.RGB);
  doc.saveAs(tempFile, saveOptions, true);
  doc.close(SaveOptions.DONOTSAVECHANGES);

  tempFile.encoding = 'BINARY';
  tempFile.open('r');
  var str = tempFile.read();
  tempFile.close();
  tempFile.remove();

  var guideTag = '8BIM' + String.fromCharCode(4) + String.fromCharCode(8);
  var tagPos = str.search(guideTag);
  var guides = new Object();

  if (tagPos != -1) {
    var tagLength = 12 + str.charCodeAt(tagPos + 11);
    var guideStr = str.substring(tagPos, tagPos+tagLength);
    guides.count = str.charCodeAt(tagPos + 27);

    var pointer = tagPos + 28;
    for (var i = 0; i < guides.count; i++) {
      //XXX fix/simplify this

      // var n = ((str[pointer] << 3) + (str[pointer] << 2) +
      //          (str[pointer] << 1) + str[pointer])/32;

      var n = Number('0x'+str.charCodeAt(pointer).toString(16)
                     +str.charCodeAt(pointer+1).toString(16)
                     +str.charCodeAt( pointer+2).toString(16)
                     +str.charCodeAt( pointer+3).toString(16))/32;
      //XXX why i+1 ???
      guides[i + 1] =  [(str.charCodeAt(pointer + 4) ? 'horz':'vert'), n];
      pointer = pointer + 5;
    }

  } else {
    guides.count = 0;
  }

  delete str;

  return guides;
};

Guide = function(guides, orient, pos) {
  var self = this;
  self.parent = guides;
  self.orient = orient;
  self.position = pos;
  self.toString = function() {
    return '[' + this.orient.name + ' ' + this.position + ']';
  };
};

Guide.prototype.typename = "Guide";

Guide.prototype.remove = function() {
  this.parent._removeGuide(this);
};

"Guides.jsx";
// EOF
Mike Hale

Get details about guides

Post by Mike Hale »

Some comments about your notes.

Code: Select all      //XXX fix/simplify this

      // var n = ((str[pointer] << 3) + (str[pointer] << 2) +
      //          (str[pointer] << 1) + str[pointer])/32;

      var n = Number('0x'+str.charCodeAt(pointer).toString(16)
                     +str.charCodeAt(pointer+1).toString(16)
                     +str.charCodeAt( pointer+2).toString(16)
                     +str.charCodeAt( pointer+3).toString(16))/32;

I would be glad to see that done better as well but I couldn't work out how to readLong from a string.

Code: Select all      //XXX why i+1 ???
      guides[i + 1] =  [(str.charCodeAt(pointer + 4) ? 'horz':'vert'), n];

Because I wanted the first guide to be guides[1]. If you think zero based would be better feel free to change it.

On a different subject, I have worked out how to close layergroups. The version I have now also reads psd file in as a string, edits that string, and rewrites the file. It works but is slow even with small psd files.

Before I spend more time working out how to edit the psd file directly do you think it would be useful? Or is this just one of those things that would be nice if Abode gave us a way?
Mike Hale

Get details about guides

Post by Mike Hale »

Also as you are cleaning this up, I just discovered that if the doc mode is bitmap, index, or Lab the saveAs will choke as those modes are not supported by jpeg. I switched from psd to jpg to speed up the script. I'm not sure the best way to handle non-jpeg modes.

And you may want to change the tag toCode: Select allvar guideTag = '8BIM\x04\x08';
xbytor

Get details about guides

Post by xbytor »

Mike Hale wrote:I would be glad to see that done better as well but I couldn't work out how to readLong from a string.

What I put in the comment is close. It's basically how it's done in Stream.js.
And I forgot the pointer offsets in the code:

Code: Select all      var n = ((str[pointer] << 3) + (str[pointer+1] << 2) +
                (str[pointer+2] << 1) + str[pointer+3])/32;



Because I wanted the first guide to be guides[1]. If you think zero based would be better feel free to change it.

I change it to 0-based, then. The only place I can recall having 1-based indexing in PS is the internal layer indexes.

On a different subject, I have worked out how to close layergroups. The version I have now also reads psd file in as a string, edits that string, and rewrites the file. It works but is slow even with small psd files.

Before I spend more time working out how to edit the psd file directly do you think it would be useful? Or is this just one of those things that would be nice if Abode gave us a way?

If you have to save and reopen a file just to close a layer group, it's not going to really be feasible. Adobe needs to step up and take care of this one.

Also as you are cleaning this up, I just discovered that if the doc mode is bitmap, index, or Lab the saveAs will choke as those modes are not supported by jpeg. I switched from psd to jpg to speed up the script. I'm not sure the best way to handle non-jpeg modes.

I added the dupe and the 8bit/RGB conversion but figured there were probably some more things that may need doing before saving to jpeg.

And you may want to change the tag

Will do.

Actually, if you want, you can take care of this round of changes. I'm slammed for the next several days.

-X
Mike Hale

Get details about guides

Post by Mike Hale »

I couldn't find much to fix. I had not noticed that you had added the doc dupe.

I added a test for bitmap, inserted your corrected code for n, change the index to 0, and added a toString method to the guides object.Code: Select allGuides.getGuideInfo = function(doc) {
  var saveOptions = new JPEGSaveOptions();
  // we don't care about image quality in the temp file
  // we do want the smallest file size
  saveOptions.quality = 0;
  var tempFile = new File(Folder.temp + '/' + File().name + '.jpg');

  // Dupe the doc, make it savable as JPEG, save it, then close the file
  doc = doc.duplicate();
  if( doc.mode == DocumentMode.BITMAP ) doc.changeMode(ChangeMode.GRAYSCALE);
  doc.bitsPerChannel = BitsPerChannelType.EIGHT;
  doc.changeMode(ChangeMode.RGB);
  doc.saveAs(tempFile, saveOptions, true);
  doc.close(SaveOptions.DONOTSAVECHANGES);

  tempFile.encoding = 'BINARY';
  tempFile.open('r');
  var str = tempFile.read();
  tempFile.close();
  tempFile.remove();

  var guideTag = '8BIM\x04\x08';
  var tagPos = str.search(guideTag);
  var guides = new Object();
  guides.toString = function(){ return 'GuideInfo'; }

  if (tagPos != -1) {
    var tagLength = 12 + str.charCodeAt(tagPos + 11);
    var guideStr = str.substring(tagPos, tagPos+tagLength);
    guides.count = str.charCodeAt(tagPos + 27);

    var pointer = tagPos + 28;
    for (var i = 0; i < guides.count; i++) {

       var n = ((str[pointer] << 3) + (str[pointer+1] << 2) +
                    (str[pointer+2] << 1) + str[pointer+3])/32;
               
      guides =  [(str.charCodeAt(pointer + 4) ? 'horz':'vert'), n];
      pointer = pointer + 5;
    }

  } else {
    guides.count = 0;
  }

  delete str;

  return guides;
};
LarsMarkelson

Get details about guides

Post by LarsMarkelson »

Hi guys,

You guys are talking about Guidelines right? I am trying some of these out, and the alert at the end keeps saying 0, even I have a single guideline placed in the middle of my image.

I keep trying xbytor's final post ITT and I get guide is undefined, Line 135.

Just trying to get something that will tell me the position of multiple guides, both horizontal and vertical.

What are you using to view the tag info where this info is stored??

Edit: :: Jaw Drop ::

apparently CS5 has this natively. Guide is now an object.
Mike Hale

Get details about guides

Post by Mike Hale »

LarsMarkelson wrote:apparently CS5 has this natively. Guide is now an object.

Yes, I wrote this code before CS5 came out. And for those who have CS5 the new guides collection is a much better way to get info about guides in a document.
jacobolus

Get details about guides

Post by jacobolus »

For what it’s worth, since CS5, it’s also possible to get the number of guides this way using Action Manager code:

Code: Select allvar $s = function (s) { return app.stringIDToTypeID(s); };
var num_guides_ref = new ActionReference;
num_guides_ref.putProperty($s('property'), $s('numberOfGuides'));
num_guides_ref.putEnumerated($s('document'), $s('ordinal'), $s('targetEnum'));
var num_guides = app.executeActionGet(num_guides_ref).getInteger($s('numberOfGuides'));

And it’s possible to get the guides by index inside the current document. What’s kind of odd about this is that 'numberOfGuides' doesn’t actually show up in the document descriptor. To get access, you have to ask for it explicitly like this. Yet another weird inconsistency. :/