Skip to main content
Participant
May 5, 2020
Answered

Help optimising a script to make a visualisation of total ink coverage

  • May 5, 2020
  • 5 replies
  • 3676 views

I have written a script where it creates a new layer from merged pixels and samples each pixel calclulates ink coverage and marks the pixel with one color if it is above the cutoff value and another if it is below. The only issue is it is really slow. I need help optimising this to improve performance. I am using photoshop CS6 JS. Below is the script

 

//samples each point in image and shows where the TAC is higher than normal (> 240)
//makes a new layer off all visible and performs the test

//CONSTANSTS

var C_ACCEPTABLE_TAC = 236
var C_ABOVE_TAC_COLOR = new SolidColor();
C_ABOVE_TAC_COLOR.rgb.hexValue = "f449ff"// bright magenta
var C_WITHIN_TAC_COLOR =  new SolidColor();
C_WITHIN_TAC_COLOR.rgb.hexValue = "ffffff"// white

//clear ES Toolkit console
if (app.name === "ExtendScript Toolkit") { 
  app.clc(); 
}
/*
// uncomment if you want ES to start even when photoshop launches the script
else {
  var estApp= BridgeTalk.getSpecifier("estoolkit");
  if(estApp) {
    var bt = new BridgeTalk;
    bt.target = estApp;
    bt.body = "app.clc()";
    bt.send();
  }
}
*/

var docRef = app.activeDocument;

function makeSelection(x,y,sw,sh){
    app.activeDocument.selection.select([ [x,y], [x,y+sh], [x+sw,y+sh], [x+sw,y] ]);  
}

//make a new layer empty layer
var newlayerRef = docRef.artLayers.add();
newlayerRef.kind = LayerKind.NORMAL;
newlayerRef.name = "IG TAC";

//set active layer to new layer
docRef.activeLayer = newlayerRef;

//copy merged (step afterwards in case only 1 layer)
docRef.selection.selectAll();
docRef.selection.copy(true) // copy merged;


//paste the selection into active layer
docRef.selection.selectAll();
docRef.paste();

//deselect selection
docRef.selection.deselect();

//process selection pixel by pixel to check TAC
app.preferences.rulerUnits = Units.PIXELS;
var docHeightPix = docRef.height
var docWidthPix = docRef.width

//$.writeln (docHeightPix)
//$.writeln (docWidthPix)

//$.writeln ("==")
for (var row=0; row <docHeightPix; row = row + 1) {
  if (row < (docHeightPix -1)) {
	  adjustedRow = row + 0.1
  }else {
	  adjustedRow = row
  }
  
  for (var col=0; col <docWidthPix; col = col + 1) {
	//var adjustedCol = (row == app.activeDocument.width -1) ? col : col + 0.1;
	if (col < (docWidthPix -1)) {
	  adjustedCol = col + 0.1
	} else {
	  adjustedCol = col;
	}
	 //$.writeln("[" + row + ", " + col + "]");
	 //$.writeln("[" + adjustedRow + ", " + adjustedCol + "]");
	 docRef.colorSamplers.removeAll();
	 docRef.colorSamplers.add([adjustedCol, adjustedRow]);//workaround for reliable pixel color picking.
	 var cs = docRef.colorSamplers[0];
	 var totalInk = cs.color.cmyk.cyan + cs.color.cmyk.magenta  + cs.color.cmyk.yellow  + cs.color.cmyk.black;
	 totalInk = Math.round(totalInk);
	 //$.writeln(totalInk);
	 // color the pixels
	makeSelection(col,row,1,1)
	 if (totalInk > C_ACCEPTABLE_TAC){
		 docRef.selection.fill(C_ABOVE_TAC_COLOR)
	 } else {
		 docRef.selection.fill(C_WITHIN_TAC_COLOR)
	 }
	 
  }
  //$.writeln("#^");
}

app.activeDocument.colorSamplers.removeAll();

.Any help would be appreaciated.

This topic has been closed for replies.
Correct answer c.pfaffenbichler

Maybe this might be of interest. 

https://community.adobe.com/t5/photoshop/maximum-total-ink/m-p/1958829?page=1

5 replies

Legend
May 6, 2020
I can offer to save the file as a Photoshop RAW file. Read this file in binary form to a string. It will be an analogue of an array of pixel values (RGB or CMYK).
Read this string character by character. Analyze values (as RGB or CMYK values). Already correct values are added to the new string character by character. Save the new string back to the file. Open the file and take a layer from it as a result.
 
c.pfaffenbichler
Community Expert
c.pfaffenbichlerCommunity ExpertCorrect answer
Community Expert
May 6, 2020
Stephen Marsh
Community Expert
Community Expert
May 7, 2020

Nice!

Stephen Marsh
Community Expert
Community Expert
May 6, 2020

Here is a quick script conversion from the action. I have used a prompt to enter the total ink limit value. I am still trying to get the variable inkCalc to convert to an integer if the prompt entry was not a whole number. Please feel free to clue me in as I have not had any success with various math methods such as parseInt etc.

 

 

// cmyk_total_ink_limits.jsx

/*
Based on the cmyk_total_ink_limits.atn from Curvemeister.com
https://curvemeister.com/downloads/cmyk_tac/index.htm

Original Curvemeister.com action converted to script using xtools
https://sourceforge.net/projects/ps-scripts/files/xtools/
https://www.ps-scripts.com/viewforum.php?f=53

Help optimizing a script to make a visualization of total ink coverage
https://community.adobe.com/t5/photoshop/help-optimising-a-script-to-make-a-visualisation-of-total-ink-coverage/td-p/11107667
*/

#target photoshop
app.bringToFront();

(function () {

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

    function CMYKTIL() {
        // Duplicate
        function step1(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putEnumerated(cTID('Dcmn'), cTID('Ordn'), cTID('Frst'));
            desc1.putReference(cTID('null'), ref1);
            executeAction(cTID('Dplc'), desc1, dialogMode);
        };

        // Flatten Image
        function step2(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            executeAction(sTID('flattenImage'), undefined, dialogMode);
        };

        // Convert Mode
        function step3(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            desc1.putInteger(cTID('Dpth'), 16);
            executeAction(sTID('convertMode'), desc1, dialogMode);
        };

        // Invert
        function step4(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            executeAction(cTID('Invr'), undefined, dialogMode);
        };

        // Curves
        function step5(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            var list1 = new ActionList();
            var desc2 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putEnumerated(cTID('Chnl'), cTID('Chnl'), cTID('Cmps'));
            desc2.putReference(cTID('Chnl'), ref1);
            var list2 = new ActionList();
            var desc3 = new ActionDescriptor();
            desc3.putDouble(cTID('Hrzn'), 0);
            desc3.putDouble(cTID('Vrtc'), 0);
            list2.putObject(cTID('Pnt '), desc3);
            var desc4 = new ActionDescriptor();
            desc4.putDouble(cTID('Hrzn'), 255);
            desc4.putDouble(cTID('Vrtc'), 64);
            list2.putObject(cTID('Pnt '), desc4);
            desc2.putList(cTID('Crv '), list2);
            list1.putObject(cTID('CrvA'), desc2);
            desc1.putList(cTID('Adjs'), list1);
            executeAction(cTID('Crvs'), desc1, dialogMode);
        };

        // Make
        function step6(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            desc1.putClass(cTID('Nw  '), cTID('Chnl'));
            var desc2 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putEnumerated(cTID('Chnl'), cTID('Chnl'), cTID('Cyn '));
            desc2.putReference(cTID('T   '), ref1);
            desc2.putEnumerated(cTID('Clcl'), cTID('Clcn'), cTID('Add '));
            desc2.putDouble(cTID('Scl '), 1.00000004749745);
            desc2.putInteger(cTID('Ofst'), 0);
            var ref2 = new ActionReference();
            ref2.putEnumerated(cTID('Chnl'), cTID('Chnl'), cTID('Mgnt'));
            desc2.putReference(cTID('Src2'), ref2);
            desc1.putObject(cTID('Usng'), cTID('Clcl'), desc2);
            executeAction(cTID('Mk  '), desc1, dialogMode);
        };

        // Make
        function step7(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            desc1.putClass(cTID('Nw  '), cTID('Chnl'));
            var desc2 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putEnumerated(cTID('Chnl'), cTID('Ordn'), cTID('Trgt'));
            desc2.putReference(cTID('T   '), ref1);
            desc2.putEnumerated(cTID('Clcl'), cTID('Clcn'), cTID('Add '));
            desc2.putDouble(cTID('Scl '), 1.00000004749745);
            desc2.putInteger(cTID('Ofst'), 0);
            var ref2 = new ActionReference();
            ref2.putEnumerated(cTID('Chnl'), cTID('Chnl'), cTID('Yllw'));
            desc2.putReference(cTID('Src2'), ref2);
            desc1.putObject(cTID('Usng'), cTID('Clcl'), desc2);
            executeAction(cTID('Mk  '), desc1, dialogMode);
        };

        // Make
        function step8(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            desc1.putClass(cTID('Nw  '), cTID('Chnl'));
            var desc2 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putEnumerated(cTID('Chnl'), cTID('Chnl'), cTID('Blck'));
            desc2.putReference(cTID('T   '), ref1);
            desc2.putEnumerated(cTID('Clcl'), cTID('Clcn'), cTID('Add '));
            desc2.putDouble(cTID('Scl '), 1.00000004749745);
            desc2.putInteger(cTID('Ofst'), 0);
            var ref2 = new ActionReference();
            ref2.putEnumerated(cTID('Chnl'), cTID('Ordn'), cTID('Trgt'));
            desc2.putReference(cTID('Src2'), ref2);
            desc1.putObject(cTID('Usng'), cTID('Clcl'), desc2);
            executeAction(cTID('Mk  '), desc1, dialogMode);
        };

        /* 
        https://curvemeister.com/downloads/cmyk_tac/index.htm
        To calculate the threshold value, divide the total ink limit by 400, and multiply by 255.
        For example, a TIL value of 300 would require a threshold value of 255 * 300/400 = 191
        */

        var inkLimitIn = prompt('Enter the total ink limit as a whole number/integer (i.e. 300):', '');
        // Test if cancel returns null, then do nothing.
        if (inkLimitIn == null) {
            alert('Script cancelled!');
            return
        };
        // Test if an empty string is returned, then do nothing.
        if (inkLimitIn == "") {
            alert('A value was not entered, script cancelled!');
            return
        };

        /* https://prepression.blogspot.com/2019/03/bridge-batch-rename-to-clean-invalid.html */
        var inkLimitOut = inkLimitIn.replace(/[^0-9]/gi, '');
        var inkCalc = 255 * inkLimitOut / 400;

        // Threshold
        function step9(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            desc1.putInteger(cTID('Lvl '), inkCalc); // Variable
            executeAction(cTID('Thrs'), desc1, dialogMode);
        };

        // Set
        function step10(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putProperty(cTID('Chnl'), sTID("selection"));
            desc1.putReference(cTID('null'), ref1);
            desc1.putEnumerated(cTID('T   '), cTID('Ordn'), cTID('Al  '));
            executeAction(cTID('setd'), desc1, dialogMode);
        };

        // Copy
        function step11(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            executeAction(cTID('copy'), undefined, dialogMode);
        };

        // Close
        function step12(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            desc1.putEnumerated(cTID('Svng'), cTID('YsN '), cTID('N   '));
            executeAction(cTID('Cls '), desc1, dialogMode);
        };

        // Set
        function step13(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putProperty(cTID('Prpr'), cTID('QucM'));
            ref1.putEnumerated(cTID('Dcmn'), cTID('Ordn'), cTID('Trgt'));
            desc1.putReference(cTID('null'), ref1);
            executeAction(cTID('setd'), desc1, dialogMode);
        };

        // Paste
        function step14(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            desc1.putEnumerated(cTID('AntA'), cTID('Annt'), cTID('Anno'));
            executeAction(cTID('past'), desc1, dialogMode);
        };

        // Clear
        function step15(enabled, withDialog) {
            if (enabled != undefined && !enabled)
                return;
            var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO);
            var desc1 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putProperty(cTID('Prpr'), cTID('QucM'));
            ref1.putEnumerated(cTID('Dcmn'), cTID('Ordn'), cTID('Trgt'));
            desc1.putReference(cTID('null'), ref1);
            executeAction(cTID('Cler'), desc1, dialogMode);
        };

        step1();      // Duplicate
        step2();      // Flatten Image
        step3();      // Convert Mode
        step4();      // Invert
        step5();      // Curves
        step6();      // Make
        step7();      // Make
        step8();      // Make
        step9();      // Threshold
        step10();      // Set
        step11();      // Copy
        step12();      // Close
        step13();      // Set
        step14();      // Paste
        step15();      // Clear
    };

    CMYKTIL.main = function () {
        CMYKTIL();
    };

    CMYKTIL.main();

    "CMYKTIL.jsx"

})();

 

 

Aiena_gAuthor
Participant
May 7, 2020

This looks interesting. One of the issues I had faced while testing out the action was that the threshold action would only accept int values. This led to more imprecise selection. I am very interested in how you converted the action into a script how could I do that on Windows?

Stephen Marsh
Community Expert
Community Expert
May 7, 2020

Just make sure that the entry into the GUI prompt is a whole number, I couldn't get the math working to clean the input correctly if one entered a float with one or more digits.

 

The original action would return a rounded down integer of 191 for a 300% total ink limit (255 * 300 / 400).

 

The script returns 191.25 against the variable (255 * inkLimitOut / 400).

 

I documented how the script was created from the action in the comments at the head, using xbytor's xtools.

 

https://sourceforge.net/projects/ps-scripts/files/xtools/

https://www.ps-scripts.com/viewforum.php?f=53

 

I would usually write the script from scratch, however, in this case, I just wanted to quickly see how workable it would be to replace the hardcoded action steps with a variable. Rather than using a prompt to enter the ink limit, a simple GUI could instead offer a dropdown-list of predefined values.

 

JJMack
Community Expert
Community Expert
May 6, 2020

You are processing each pixels in a document one at time in a script that need to have its code interpret for each pixel process. an image 1,000px x 1,000px at 300PPI would be 3.3" by 3.3" printed image that contains 1,000,000 pixels.  How long  and hard would  you have to work to make 1$ for each pixel.  You can not expect a script to do a million calculations in a short amount of time. The would required a compiled program optimized  for your particular calculation.  You  should look for compiled plug-in that does what you want. If you want to process a Camera digit image.  Phones these days capture 12,000,000 pixels for an image.  A 4K TV image is 8MP, a 8K TV image is 33MP.  How long would it take you to twiddle your thumbs 1,000,000 rotations.

JJMack
Aiena_gAuthor
Participant
May 6, 2020

Yes makes sense. I was having a look at numpy and python to do the calculations faster. But thanks to Stephen on top that photoshop action works perfectly.

Stephen Marsh
Community Expert
Community Expert
May 6, 2020

I have not tested your script, however, you may be interested in looking at this action;

 

https://curvemeister.com/downloads/cmyk_tac/index.htm

Aiena_gAuthor
Participant
May 6, 2020

Thank you so much. It seems to work really well.. The roundtrip to acrobat and back is very time consuming.