Copy link to clipboard
Copied
In the MacIntosh-forum recently somebody asked about a way to assess the maximal total ink of an image in Photoshop as in Acrobat or Indesign (probably for prepress-purposes I guess).
http://forums.adobe.com/thread/453851;jsessionid=6EFF73D4ADC319F2F24387656080AF46.node0?tstart=0
My first idea was iterating a color-picker through every pixel of an image an collecting the values, but that seems to take a forbiddingly long time.
A way that works much quicker is bunching the four CMYK-channels into one channel by Linear Burning them with 25% opacity and evaluating the resulting channel’s histogram.
Of course that means that every histogram-step actually amounts for about 1.5% accumulated ink which still seems acceptable to me.
And with an appropriate Threshold-setting the channel can be adapted to indicate areas of certain minimal ink amounts, though non interactively.
But when working on CMYK-files with more than one additional spot color the rounding errors accumulate pretty strongly as with 6 colors one histogram-step would amount to about 2.3% total ink and the opacity for the calculation of the accumulated-ink-channel – while fine for 4 or 5 colors – has to be rounded to an integer for 6 colors.
This can be worked around to some extent by getting the channels together as layers in a grayscale-document, converting them to one Smart Object and applying the Stack Mode Mean and evaluating the result.
So I have a crude and inexact solution but does any one of You have a better idea on how to get the maximum total ink in an image with more than 4 colors in a reasonable time or does a Script for that already exist?
(If someone’s interested I can post my current Script.)
Copy link to clipboard
Copied
/* creates layers that mark areas that more or less exceed a certain total area coverage;
the more channels the more pronounced the rounding issues;
based on a discussion with j maloney;
2013, use it at your own risk */
if (app.documents.length > 0 && app.activeDocument.mode == DocumentMode.CMYK) {
/* dialog;*/
var dlg = new Window("dialog", "indicate tac of and over", [500,300,750,420]);
/* filter for checking if entry is numeric, thanks to xbytor;*/
numberKeystrokeFilter = function() {
if (this.text.match(/[^\-\.\d]/)) {
this.text = this.text.replace(/[^\d]/g, "")
};
if (this.text <= 0) {this.text = 100};
if (Number(this.text) > 500) {this.text = "500"}
};
/* field for entry;*/
dlg.tac = dlg.add("edittext", [15,15,110,35], "300", {multiline:false});
dlg.tac.active = true;
dlg.tac.onChange = numberKeystrokeFilter;
/* warning;*/
dlg.warning = dlg.add("statictext", [13,78,240,150], "please remember to remove or");
dlg.warning2 = dlg.add("statictext", [13,95,240,150], "hide the indicator layers");
/* ok- and cancel-button;*/
dlg.buildBtn = dlg.add("button", [13,45,118,68], "OK", {name:"ok"});
dlg.cancelBtn = dlg.add("button", [128,45,240,68], "Cancel", {name:"cancel"});
/* show dialog;*/
dlg.center();
/* show dialog;*/
var myReturn = dlg.show ();
if (myReturn == true) {
var myDocument = app.activeDocument;
/* get the number of tac;*/
var total = Number (dlg.tac.text);
var myDocument = app.activeDocument;
/* go to top layer;*/
var theVis = myDocument.layers[0].visible;
myDocument.activeLayer = myDocument.layers[0];
if (myDocument.layers[0].iBackgroundLayer == false) {myDocument.layers[0].visible = theVis};
/* set to composite;*/
app.activeDocument.activeChannels = [app.activeDocument.channels[0], app.activeDocument.channels[1], app.activeDocument.channels[2], app.activeDocument.channels[3]];
/* array for printing channels;*/
var theChannels = [0, 1, 2, 3];
/* check for spot colors;*/
for (var m = myDocument.channels.length - 1; m >= 4 ; m--) {
if (myDocument.channels
theChannels = theChannels.concat(m)
}
};
/* create channel mixer;*/
var channelMixer = theChannelMix(theChannels.length);
channelMixer.name = "total area coverage "+total+"%";
/* work throuh spot channels;*/
for (var n = theChannels.length - 1; n >= 4 ; n--) {
myDocument.selection.load(myDocument.channels[theChannels
/* make solid color layer;*/
var idMk = charIDToTypeID( "Mk " );
var desc72 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref57 = new ActionReference();
var idcontentLayer = stringIDToTypeID( "contentLayer" );
ref57.putClass( idcontentLayer );
desc72.putReference( idnull, ref57 );
var idUsng = charIDToTypeID( "Usng" );
var desc73 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var desc74 = new ActionDescriptor();
var idClr = charIDToTypeID( "Clr " );
var desc75 = new ActionDescriptor();
var idCyn = charIDToTypeID( "Cyn " );
desc75.putDouble( idCyn, 0.000000 );
var idMgnt = charIDToTypeID( "Mgnt" );
desc75.putDouble( idMgnt, 100.000000 );
var idYlw = charIDToTypeID( "Ylw " );
desc75.putDouble( idYlw, 0.000000 );
var idBlck = charIDToTypeID( "Blck" );
desc75.putDouble( idBlck, 0.000000 );
var idCMYC = charIDToTypeID( "CMYC" );
desc74.putObject( idClr, idCMYC, desc75 );
var idsolidColorLayer = stringIDToTypeID( "solidColorLayer" );
desc73.putObject( idType, idsolidColorLayer, desc74 );
var idcontentLayer = stringIDToTypeID( "contentLayer" );
desc72.putObject( idUsng, idcontentLayer, desc73 );
executeAction( idMk, desc72, DialogModes.NO );
myDocument.activeLayer.name = myDocument.channels[theChannels
myDocument.activeLayer.opacity = Math.ceil(100 / theChannels.length);
myDocument.activeLayer.grouped = true;
myDocument.channels[theChannels
};
/* create curves layer;*/
var curvesLayer = theCurvesMix (total, theChannels.length);
}
};
/* function for channel mixer */
function theChannelMix (channelNumber) {
var thePerc = Math.ceil(100 / channelNumber);
var idMk = charIDToTypeID( "Mk " );
var desc3 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref2 = new ActionReference();
var idAdjL = charIDToTypeID( "AdjL" );
ref2.putClass( idAdjL );
desc3.putReference( idnull, ref2 );
var idUsng = charIDToTypeID( "Usng" );
var desc4 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var desc5 = new ActionDescriptor();
var idpresetKind = stringIDToTypeID( "presetKind" );
var idpresetKindType = stringIDToTypeID( "presetKindType" );
var idpresetKindDefault = stringIDToTypeID( "presetKindDefault" );
desc5.putEnumerated( idpresetKind, idpresetKindType, idpresetKindDefault );
var idCyn = charIDToTypeID( "Cyn " );
var desc6 = new ActionDescriptor();
var idCyn = charIDToTypeID( "Cyn " );
var idPrc = charIDToTypeID( "#Prc" );
desc6.putUnitDouble( idCyn, idPrc, 100.000000 );
var idChMx = charIDToTypeID( "ChMx" );
desc5.putObject( idCyn, idChMx, desc6 );
var idMgnt = charIDToTypeID( "Mgnt" );
var desc7 = new ActionDescriptor();
var idMgnt = charIDToTypeID( "Mgnt" );
var idPrc = charIDToTypeID( "#Prc" );
desc7.putUnitDouble( idMgnt, idPrc, 100.000000 );
var idChMx = charIDToTypeID( "ChMx" );
desc5.putObject( idMgnt, idChMx, desc7 );
var idYlw = charIDToTypeID( "Ylw " );
var desc8 = new ActionDescriptor();
var idYlw = charIDToTypeID( "Ylw " );
var idPrc = charIDToTypeID( "#Prc" );
desc8.putUnitDouble( idYlw, idPrc, 100.000000 );
var idChMx = charIDToTypeID( "ChMx" );
desc5.putObject( idYlw, idChMx, desc8 );
var idBlck = charIDToTypeID( "Blck" );
var desc9 = new ActionDescriptor();
var idBlck = charIDToTypeID( "Blck" );
var idPrc = charIDToTypeID( "#Prc" );
desc9.putUnitDouble( idBlck, idPrc, 100.000000 );
var idChMx = charIDToTypeID( "ChMx" );
desc5.putObject( idBlck, idChMx, desc9 );
var idChnM = charIDToTypeID( "ChnM" );
desc4.putObject( idType, idChnM, desc5 );
var idAdjL = charIDToTypeID( "AdjL" );
desc3.putObject( idUsng, idAdjL, desc4 );
executeAction( idMk, desc3, DialogModes.NO );
var idsetd = charIDToTypeID( "setd" );
var desc38 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref12 = new ActionReference();
var idAdjL = charIDToTypeID( "AdjL" );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref12.putEnumerated( idAdjL, idOrdn, idTrgt );
desc38.putReference( idnull, ref12 );
var idT = charIDToTypeID( "T " );
var desc39 = new ActionDescriptor();
var idpresetKind = stringIDToTypeID( "presetKind" );
var idpresetKindType = stringIDToTypeID( "presetKindType" );
var idpresetKindCustom = stringIDToTypeID( "presetKindCustom" );
desc39.putEnumerated( idpresetKind, idpresetKindType, idpresetKindCustom );
var idCyn = charIDToTypeID( "Cyn " );
var desc40 = new ActionDescriptor();
var idCyn = charIDToTypeID( "Cyn " );
var idPrc = charIDToTypeID( "#Prc" );
desc40.putUnitDouble( idCyn, idPrc, 0.000000 );
var idChMx = charIDToTypeID( "ChMx" );
desc39.putObject( idCyn, idChMx, desc40 );
var idMgnt = charIDToTypeID( "Mgnt" );
var desc41 = new ActionDescriptor();
var idCyn = charIDToTypeID( "Cyn " );
var idPrc = charIDToTypeID( "#Prc" );
desc41.putUnitDouble( idCyn, idPrc, thePerc );
var idMgnt = charIDToTypeID( "Mgnt" );
var idPrc = charIDToTypeID( "#Prc" );
desc41.putUnitDouble( idMgnt, idPrc, thePerc );
var idYlw = charIDToTypeID( "Ylw " );
var idPrc = charIDToTypeID( "#Prc" );
desc41.putUnitDouble( idYlw, idPrc, thePerc );
var idBlck = charIDToTypeID( "Blck" );
var idPrc = charIDToTypeID( "#Prc" );
desc41.putUnitDouble( idBlck, idPrc, thePerc );
var idChMx = charIDToTypeID( "ChMx" );
desc39.putObject( idMgnt, idChMx, desc41 );
var idYlw = charIDToTypeID( "Ylw " );
var desc42 = new ActionDescriptor();
var idYlw = charIDToTypeID( "Ylw " );
var idPrc = charIDToTypeID( "#Prc" );
desc42.putUnitDouble( idYlw, idPrc, 0.000000 );
var idChMx = charIDToTypeID( "ChMx" );
desc39.putObject( idYlw, idChMx, desc42 );
var idBlck = charIDToTypeID( "Blck" );
var desc43 = new ActionDescriptor();
var idBlck = charIDToTypeID( "Blck" );
var idPrc = charIDToTypeID( "#Prc" );
desc43.putUnitDouble( idBlck, idPrc, 0.000000 );
var idChMx = charIDToTypeID( "ChMx" );
desc39.putObject( idBlck, idChMx, desc43 );
var idChnM = charIDToTypeID( "ChnM" );
desc38.putObject( idT, idChnM, desc39 );
executeAction( idsetd, desc38, DialogModes.NO );
var idsetd = charIDToTypeID( "setd" );
var desc34 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref22 = new ActionReference();
var idLyr = charIDToTypeID( "Lyr " );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref22.putEnumerated( idLyr, idOrdn, idTrgt );
desc34.putReference( idnull, ref22 );
var idT = charIDToTypeID( "T " );
var desc35 = new ActionDescriptor();
var idBlnd = charIDToTypeID( "Blnd" );
var list10 = new ActionList();
var desc36 = new ActionDescriptor();
var idChnl = charIDToTypeID( "Chnl" );
var ref23 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
var idChnl = charIDToTypeID( "Chnl" );
var idMgnt = charIDToTypeID( "Mgnt" );
ref23.putEnumerated( idChnl, idChnl, idMgnt );
desc36.putReference( idChnl, ref23 );
var idSrcB = charIDToTypeID( "SrcB" );
desc36.putInteger( idSrcB, 0 );
var idSrcl = charIDToTypeID( "Srcl" );
desc36.putInteger( idSrcl, 0 );
var idSrcW = charIDToTypeID( "SrcW" );
desc36.putInteger( idSrcW, 62 );
var idSrcm = charIDToTypeID( "Srcm" );
desc36.putInteger( idSrcm, 62 );
var idDstB = charIDToTypeID( "DstB" );
desc36.putInteger( idDstB, 0 );
var idDstl = charIDToTypeID( "Dstl" );
desc36.putInteger( idDstl, 0 );
var idDstW = charIDToTypeID( "DstW" );
desc36.putInteger( idDstW, 255 );
var idDstt = charIDToTypeID( "Dstt" );
desc36.putInteger( idDstt, 255 );
var idBlnd = charIDToTypeID( "Blnd" );
list10.putObject( idBlnd, desc36 );
desc35.putList( idBlnd, list10 );
var idLefx = charIDToTypeID( "Lefx" );
var desc37 = new ActionDescriptor();
var idScl = charIDToTypeID( "Scl " );
var idPrc = charIDToTypeID( "#Prc" );
desc37.putUnitDouble( idScl, idPrc, 100.000000 );
var idLefx = charIDToTypeID( "Lefx" );
desc35.putObject( idLefx, idLefx, desc37 );
var idLyr = charIDToTypeID( "Lyr " );
desc34.putObject( idT, idLyr, desc35 );
executeAction( idsetd, desc34, DialogModes.NO );
return app.activeDocument.activeLayer
};
/* function for curves */
function theCurvesMix (total, channelNumber) {
var thePoint = 255 - (256 / channelNumber * total / 100);
var idMk = charIDToTypeID( "Mk " );
var desc7 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref4 = new ActionReference();
var idAdjL = charIDToTypeID( "AdjL" );
ref4.putClass( idAdjL );
desc7.putReference( idnull, ref4 );
var idUsng = charIDToTypeID( "Usng" );
var desc8 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var desc9 = new ActionDescriptor();
var idpresetKind = stringIDToTypeID( "presetKind" );
var idpresetKindType = stringIDToTypeID( "presetKindType" );
var idpresetKindDefault = stringIDToTypeID( "presetKindDefault" );
desc9.putEnumerated( idpresetKind, idpresetKindType, idpresetKindDefault );
var idCrvs = charIDToTypeID( "Crvs" );
desc8.putObject( idType, idCrvs, desc9 );
var idAdjL = charIDToTypeID( "AdjL" );
desc7.putObject( idUsng, idAdjL, desc8 );
executeAction( idMk, desc7, DialogModes.NO );
var idsetd = charIDToTypeID( "setd" );
var desc17 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref9 = new ActionReference();
var idAdjL = charIDToTypeID( "AdjL" );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref9.putEnumerated( idAdjL, idOrdn, idTrgt );
desc17.putReference( idnull, ref9 );
var idT = charIDToTypeID( "T " );
var desc18 = new ActionDescriptor();
var idpresetKind = stringIDToTypeID( "presetKind" );
var idpresetKindType = stringIDToTypeID( "presetKindType" );
var idpresetKindCustom = stringIDToTypeID( "presetKindCustom" );
desc18.putEnumerated( idpresetKind, idpresetKindType, idpresetKindCustom );
var idAdjs = charIDToTypeID( "Adjs" );
var list4 = new ActionList();
var desc19 = new ActionDescriptor();
var idChnl = charIDToTypeID( "Chnl" );
var ref10 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
var idChnl = charIDToTypeID( "Chnl" );
var idCmps = charIDToTypeID( "Cmps" );
ref10.putEnumerated( idChnl, idChnl, idCmps );
desc19.putReference( idChnl, ref10 );
var idCrv = charIDToTypeID( "Crv " );
var list5 = new ActionList();
var desc20 = new ActionDescriptor();
var idHrzn = charIDToTypeID( "Hrzn" );
desc20.putDouble( idHrzn, thePoint );
var idVrtc = charIDToTypeID( "Vrtc" );
desc20.putDouble( idVrtc, 0.000000 );
var idPnt = charIDToTypeID( "Pnt " );
list5.putObject( idPnt, desc20 );
var desc21 = new ActionDescriptor();
var idHrzn = charIDToTypeID( "Hrzn" );
desc21.putDouble( idHrzn, thePoint + 4 );
var idVrtc = charIDToTypeID( "Vrtc" );
desc21.putDouble( idVrtc, 255.000000 );
var idPnt = charIDToTypeID( "Pnt " );
list5.putObject( idPnt, desc21 );
desc19.putList( idCrv, list5 );
var idCrvA = charIDToTypeID( "CrvA" );
list4.putObject( idCrvA, desc19 );
desc18.putList( idAdjs, list4 );
var idCrvs = charIDToTypeID( "Crvs" );
desc17.putObject( idT, idCrvs, desc18 );
executeAction( idsetd, desc17, DialogModes.NO );
app.activeDocument.activeLayer.grouped = true;
return app.activeDocument.activeLayer
};
Copy link to clipboard
Copied
c.pfaffenbichler,
This is fantastic stuff – I've been looking for something like this for a while, I'm sure that others have wondered over the years (before RGB workflow took hold) why this was not an interactive, built-in feature like the 'gamut warning'. Creating a plug-in takes 30 minutes of time for an experienced employee!
https://forums.adobe.com/thread/574683
We'd all like to work in RGB, then convert to profile at the last stage to avoid overinking. I for one still have to correct some files in a CMYK workflow (some clients still insist on this). I've been using a script which places the image file in an InDesign layout with Ink Limit preview on, which updates after each save. It works for me, but this is much more efficient for sure.
Kind regards
gr
Copy link to clipboard
Copied