Copy link to clipboard
Copied
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.
I have not tested your script, however, you may be interested in looking at this action;
Thank you so much. It seems to work really well.. The roundtrip to acrobat and back is very time consuming.
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 Curvem
...
Maybe this might be of interest.
https://community.adobe.com/t5/photoshop/maximum-total-ink/m-p/1958829?page=1
Copy link to clipboard
Copied
I have not tested your script, however, you may be interested in looking at this action;
Copy link to clipboard
Copied
Thank you so much. It seems to work really well.. The roundtrip to acrobat and back is very time consuming.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
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"
})();
Copy link to clipboard
Copied
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?
Copy link to clipboard
Copied
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.
Copy link to clipboard
Copied
Maybe this might be of interest.
https://community.adobe.com/t5/photoshop/maximum-total-ink/m-p/1958829?page=1
Copy link to clipboard
Copied
Nice!
Copy link to clipboard
Copied