Skip to main content
Inspiring
April 2, 2010
Answered

Determining when an adjustment layer holds information.

  • April 2, 2010
  • 5 replies
  • 3667 views

Is it possible using javascript to determine when an adjustment layer holds information? I'll go into some detail as to what I mean.

I run a combination script/actions to set up a bunch of layers based on what I'm doing. Not all layers will get used in any given image. For example, when I'm retouching a model's photo, I run one that sets up a bunch of adjustment layers to enhance the color in the models eyes, or to change the color depending on what I'm doing. This sets up seven layers, each for a different color, then a few for highlights and/or catchlights. I may use between 0 and 3 of these layers. Each adjustment layer has an inverse mask (black) which I paint white in the areas I want to enhance.

Now, is there an easy way to determine which of these masks are pure black and which have been painted onto? Also, is there an easy way to determine on an adjustment layer such as curves, levels, hue/saturation, etc. whether any adjustments have been made or if they are still in default? I'm guessing the best way would be to first check the mask to see if it's not all black and if it isn't, then check for adjustments.

This topic has been closed for replies.
Correct answer Michael_L_Hale

WOW! This is fantstic.

I typically use the following that I'd need to know if any adjustments are made:

Curves

Levels

Hue/Saturation

Color Balance

Black & White

I also use a few Photofilters but typically those are either masked over or partly unmasked so I won't need to know of any adjustments to that.

For Hue/Saturation I probably wouldn't need to know of any deviation from default either since those are set default until I unmask areas.

Thanks again, Mike. I appreciate the efffort you're putting into this.


I was not really happy with the way I was testing for default settings. I was afraid that the simple replace text I was using might fail in some cases. It turns out that I was right. So here is a new set of functions. These I feel good about using.

All of the functions need this CRC code.

/************************************************************************************************

  JavaScript source code        March 10, 2006

  Copyright (c) 2003-2006 Scandinavian Digital Systems AB

  Internet: http://www.digsys.se

  Freeware: The source code and its methods and algorithms may be
            used as desired without restrictions.

************************************************************************************************/

/*
  CRC-32 (as it is in ZMODEM) in table form

  Copyright (C) 1986 Gary S. Brown. You may use this program, or
  code or tables extracted from it, as desired without restriction.

  Modified by Anders Danielsson, February 5, 1989 and March 10, 2006.

  This is also known as FCS-32 (as it is in PPP), described in
  RFC-1662 by William Allen Simpson, see RFC-1662 for references.
*/
var Crc32Tab = new Array( /* CRC polynomial 0xEDB88320 */
/*
  C/C++ language:

  unsigned long Crc32Tab[] = {...};
*/
0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D);

function Crc32Add(crc,c)
/*
  'crc' should be initialized to 0xFFFFFFFF and after the computation it should be
  complemented (inverted).

  CRC-32 is also known as FCS-32.

  If the FCS-32 is calculated over the data and over the complemented FCS-32, the
  result will always be 0xDEBB20E3 (without the complementation).
*/
{
  return Crc32Tab[(crc^c)&0xFF]^((crc>>8)&0xFFFFFF);
}
/*
  C/C++ language:

  inline unsigned long Crc32Add(unsigned long crc, unsigned char c)
  {
    return Crc32Tab[(unsigned char)crc^c]^(crc>>8);
  }
*/

/*
  Application functions that calculates CRC of a string
*/

function Crc32Str(str)
{
  var n;
  var len=str.length;
  var crc;

  crc=0xFFFFFFFF;
  for (n=0; n<len; n++)
  {
    crc=Crc32Add(crc,str.charCodeAt(n));
  }
  return crc^0xFFFFFFFF;
}

/*
  Hex convert functions
*/

function Hex32(val)
/*
  Convert value as 32-bit unsigned integer to 8 digit hexadecimal number prefixed with "0x".
*/
{
  var n;
  var str1;
  var str2;

  n=val&0xFFFF;
  str1=n.toString(16).toUpperCase();
  while (str1.length<4)
  {
    str1="0"+str1;
  }
  n=(val>>>16)&0xFFFF;
  str2=n.toString(16).toUpperCase();
  while (str2.length<4)
  {
    str2="0"+str2;
  }
  return "0x"+str2+str1;
}

isDefaultHueSatAdjustment();
function isDefaultHueSatAdjustment(){
     if(app.activeDocument.activeLayer.kind != LayerKind.HUESATURATION ) return;
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
     var desc = executeActionGet(ref).getList(stringIDToTypeID('adjustment')).getObjectValue(0);
     var rawData = desc.getData(stringIDToTypeID('legacyContentData'));
     var str = Hex32(Crc32Str(rawData));
     if( str == '0xB83ED6B1' ) return true;
     return false;
}
isDefaultColorBalanceAdjustment();
function isDefaultColorBalanceAdjustment(){
     if(app.activeDocument.activeLayer.kind != LayerKind.COLORBALANCE ) return;
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
     var desc = executeActionGet(ref).getList(stringIDToTypeID('adjustment')).getObjectValue(0);
     var rawData = desc.getData(stringIDToTypeID('legacyContentData'));
     var str = Hex32(Crc32Str(rawData));
     if( str == '0x16CEAACC' ) return true;
     return false;
}

isDefaultCurvesAdjustment();
function isDefaultCurvesAdjustment(){
     if(app.activeDocument.activeLayer.kind !=  LayerKind.CURVES ) return;
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
     var desc = executeActionGet(ref).getList(stringIDToTypeID('adjustment')).getObjectValue(0);
     var rawData = desc.getData(stringIDToTypeID('legacyContentData'));
     var str = Hex32(Crc32Str(rawData));
     if( str == '0xF5315AF8' ) return true;
     return false;
}

isDefaultLevelsAdjustment();
function isDefaultLevelsAdjustment(){
     if(app.activeDocument.activeLayer.kind != LayerKind.LEVELS ) return;
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
     var desc = executeActionGet(ref).getList(stringIDToTypeID('adjustment')).getObjectValue(0);
     var rawData = desc.getData(stringIDToTypeID('legacyContentData'));
     var str = Hex32(Crc32Str(rawData));
     if( str == '0x02A5FFD5' ) return true;
     return false;
}

isDefaultBWAdjustment();
function isDefaultBWAdjustment(){
     if(app.activeDocument.activeLayer.kind != LayerKind.BLACKANDWHITE ) return;
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
     var desc = executeActionGet(ref).getList(stringIDToTypeID('adjustment')).getObjectValue(0);
     var rawData = desc.getData(stringIDToTypeID('legacyContentData'));
     var str = Hex32(Crc32Str(rawData));
     if( str == '0x8BE5167D' ) return true;
     return false;
}

5 replies

jugenjuryAuthor
Inspiring
June 17, 2010

You're gonna think I'm crazy, but I swear I tried the .name first and got an error that there is no such element.

Of course, that could have been for something else, too.

Thank you guys so much.

jugenjuryAuthor
Inspiring
June 17, 2010

Thanks, guys.

I must be doing something wrong, though. Here's what I have:

docRef=app.activeDocument;
docs=app.documents;
d=docs.length;

function copyadj(document) {
    var desc17 = new ActionDescriptor();
    var ref5 = new ActionReference();
    ref5.putEnumerated( app.charIDToTypeID('Lyr '), app.charIDToTypeID('Ordn'), app.charIDToTypeID('Trgt') );
    desc17.putReference( app.charIDToTypeID('null'), ref5 );
    var ref6 = new ActionReference();
    ref6.putName( app.charIDToTypeID('Dcmn'), document);
    desc17.putReference( app.charIDToTypeID('T   '), ref6 );
    desc17.putInteger( app.charIDToTypeID('Vrsn'), 5 );
    executeAction( app.charIDToTypeID('Dplc'), desc17, DialogModes.NO );
};

for (i=d-1;i>0;i--) {
    if (docs!=docRef) {
        copyadj(docs);
    }
}

I tried using doc=new String(docs); and sending doc to the copyadj() function but I keep getting errors.

The script as it is here gives me the error that the document "Shanna_100602_0091.CR2" is not available. If I put the same document name in quotes in the ref6 line in place of document:

ref6.putName( app.charIDToTypeID('Dcmn'), "Shanna_100602_0091.CR2");

Then it works. Any thoughts? It's probably something simple I'm missing.

Inspiring
June 17, 2010
        copyadj(docs.name);
jugenjuryAuthor
Inspiring
June 17, 2010

OK. I figured this out using a preset.

Reading the raw data from the adjustment layer and dumping it into a file descriptor, then using that as a preset to load the settings into another adjustment layer works fine. All I have to do is remove() the file at the end of the script.

It would still be nice to know how to just plug this raw data into another adjustment layer of the same type.

Inspiring
June 17, 2010

jugenjury wrote:

It would still be nice to know how to just plug this raw data into another adjustment layer of the same type.

You can copy the whole layer including effects and masks with either X's function or with the DOM layer duplicate method. You can also use scriptlistner to move or copy the effect from one layer to another in the same doc.

To copy just the effect from one layer in one document to a layer in a different you can get the effects descriptor from the source layer, switch docs, then set that descriptor to the target layer. For that you need to write the Action Manager code as I could not get. Note I'm not sure this is what you want. It copies all the layer effects but does not copy blendmode, opacity, fill opacity, or the blend if settings.

function getLayerEffectsDescriptor(){
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
     var desc = executeActionGet(ref);
     if(desc.hasKey(stringIDToTypeID('layerEffects'))) return desc.getObjectValue(stringIDToTypeID('layerEffects'));
};
function setLayerEffectsDescriptor( effectsDesc ) {
    var desc = new ActionDescriptor();
        var ref = new ActionReference();
        ref.putProperty( charIDToTypeID('Prpr'), charIDToTypeID('Lefx') );
        ref.putEnumerated( charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
    desc.putReference( charIDToTypeID('null'), ref );
    desc.putObject( charIDToTypeID('T   '), charIDToTypeID('Lefx'), effectsDesc );
    executeAction( charIDToTypeID('setd'), desc, DialogModes.NO );
};

activeDocument = app.documents[1];
var layerFxDesc = getLayerEffectsDescriptor();
activeDocument = app.documents[0];
setLayerEffectsDescriptor( layerFxDesc );

jugenjuryAuthor
Inspiring
June 17, 2010

Sorry to bring up old topic again.

Thanks again to Mike for this code. I wish I understood the construction of ScriptListener/Action Events code better. Maybe in time.

What I'm looking to do now is take this raw data from an adjustment layer and write it to another adjustment layer of the same type so all the settings will match. Basically copying an adjustment layer. I also understand I could just duplicate the layer, but this only works within the current document and I'd like to be able to transfer it to another document also.

I'm fairly certain I could do it by saving a custom preset of the current adj layer, then create a new layer and set it from that preset file. I'd prefer it not create a custom preset file, though. So if the raw data can't be used to set it, I'll go that route.

Is this possible and if so, how?

Inspiring
June 17, 2010

You can copy layers from one doc to another. Here's some sample SL code. I created two new docs in PS, added a Curves adjustment layer mto the second, then copied it back to the first.

function ftn1() {
  function cTID(s) { return app.charIDToTypeID(s); };
  function sTID(s) { return app.stringIDToTypeID(s); };

    var desc17 = new ActionDescriptor();
        var ref5 = new ActionReference();
        ref5.putEnumerated( cTID('Lyr '), cTID('Ordn'), cTID('Trgt') );
    desc17.putReference( cTID('null'), ref5 );
        var ref6 = new ActionReference();
        ref6.putName( cTID('Dcmn'), "Untitled-1" );
    desc17.putReference( cTID('T   '), ref6 );
    desc17.putInteger( cTID('Vrsn'), 5 );
    executeAction( cTID('Dplc'), desc17, DialogModes.NO );
};

ftn1();

jugenjuryAuthor
Inspiring
June 17, 2010

Very interesting. I honestly didn't think to try it that way.

I'm sure the code I wrote so far will be useful somewhere.

Thanks, xbytor. This will save me a lot of time.

jugenjuryAuthor
Inspiring
April 2, 2010

OK. I did some playing around. It seems that an untouched mask (normal or inverted) holds 0 bytes of information. When the mask is deleted, the Doc filesize in the info panel doesn't change but when I paint onto the mask, the filesize goes up. Using a history state check by deleting the mask and comparing document filesize before and after might work for that part. How would I know whether it's a normal or inverted mask, though?

Inspiring
April 2, 2010

What I would do is check the historgam of the mask. From that you can tell if it's all white, all black, or has 'edits'.

For the adjustment data itself, what version of Photoshop are you using? At one time you could get the adjustment used somewhat easy from the layer's action descriptor. But now that descriptor has the data in a raw format. I have not worked out how to read that raw data but if you need to do so I can try to help.

jugenjuryAuthor
Inspiring
April 3, 2010

Thanks, Mike. I'm using CS4. So I would loop through the 256 items in the histogram array and check if there are any values above 0, correct? Or is there a property that holds the sum of all the array elements? If the value is anything other than all black, I would then check for adjustments.

The only other option I can think of for now in figuring out default adjustment values is to maybe create a data file that holds the types and expected values to compare with. I think that's similar to what you're talking about. Would this really need to evaluate the data or could it be used to just compare raw data from a default layer to raw data from the unknown. This is beyond anything I've scripted so far.