Copy link to clipboard
Copied
Hi,
For an image of any size/dimensions, I'm trying to automatically extract every pixel's color information by coordinate, ideally the Lab values. So the output would look something like this:
x y L a b
0 0 100 0 0
0 1 99 0 0
etc. The output would ideally be in CSV format.
I've seen some pretty outstanding scripts which are similar to what I need (but not quite) and I'm also a complete scripting novice so not sure where to begin! Any help gratefully received!
Oh and I'm on PS3 Extended.
Cheers
Steve
Ok, this script assumes
There is an open document that is 8bit Lab mode
The script will create a CSV file in the same location as the open document (if it had not been saved it will be saved to the desktop) with the same name as the open document.
...File.prototype.readByte = function() {
return this.read(1).charCodeAt(0);
};
function convertByteToSignedByte(num){
var dec = (num | (num % 256))-128
return dec;
};
function convertByteToLum( num){
var dec = Math.round((num/256)*100);
return
Copy link to clipboard
Copied
If the images are of any »serious« size I’m afraid what you are asking is, while possible, probably not sensible as iterating the color picker through all the pixels would be a rather timeconsuming practice.
May I ask what is the reason behind your request, what are you trying to achieve exactly?
Copy link to clipboard
Copied
This might be better saving the document as a Raw file then converting that to a CSV file.
Copy link to clipboard
Copied
Ok, this script assumes
There is an open document that is 8bit Lab mode
The script will create a CSV file in the same location as the open document (if it had not been saved it will be saved to the desktop) with the same name as the open document.
File.prototype.readByte = function() {
return this.read(1).charCodeAt(0);
};
function convertByteToSignedByte(num){
var dec = (num | (num % 256))-128
return dec;
};
function convertByteToLum( num){
var dec = Math.round((num/256)*100);
return dec;
};function main(){
if(!documents.length) return;
if(activeDocument.mode != DocumentMode.LAB) {
alert("Please convert this document to Lab mode\r Then re-run this script");
return;
}
var Name = decodeURI(app.activeDocument.name).replace(/\.[^\.]+$/, '');
try{
var Path = activeDocument.path;
}catch(e){var Path = '~/Desktop';}
var rawFile = new File(Path +"/"+Name +".raw");
saveAsRaw(rawFile);
var csvFile = new File(Path +"/"+Name +".csv");
var len = 0;
var W = activeDocument.width.as('px');
CountW=0;
CountH=0;
rawFile.encoding = 'BINARY';
rawFile.open('r');
csvFile.open('w');
csvFile.writeln('X,Y,L,a,b');
while(!rawFile.eof){
csvFile.write(CountW+',');
csvFile.write(CountH+',');
csvFile.write(convertByteToLum(rawFile.readByte())+',');
csvFile.write(convertByteToSignedByte(rawFile.readByte())+',');
csvFile.writeln(convertByteToSignedByte(rawFile.readByte()));
len++;
if(len % W == 0){
CountH++;
CountW=0;
}else{
CountW++;
}
}
rawFile.close();
csvFile.close();
rawFile.remove();
}
main();
function saveAsRaw(file) {
var desc1 = new ActionDescriptor();
var desc2 = new ActionDescriptor();
desc2.putString( charIDToTypeID('FlCr'), "8BIM" );
desc2.putBoolean( charIDToTypeID('ChnI'), true );
desc1.putObject( charIDToTypeID('As '), charIDToTypeID('Rw '), desc2 );
desc1.putPath( charIDToTypeID('In '), new File( file ) );
desc1.putBoolean( charIDToTypeID('Cpy '), true );
executeAction( charIDToTypeID('save'), desc1, DialogModes.NO );
};
Copy link to clipboard
Copied
This is just what I was looking for! Amazing! Thank you very much for your quick response!
Copy link to clipboard
Copied
Hi Paul,
I didn't have time to look at this when you posted it the other day and this may be kind of picky but I think the function convertByteToSignedByte() could be better named incase someone wanted to use it in another script. It doesn't covert a unsigned byte into a signed byte ( two's complement ) but rather converts an unsigned byte into the values Adobe uses for the a and b channels for a document in Lab mode.
The function works fine for what it is doing here but if someone used it for other types of binary reading it will most likely give the wrong value. I only bring this up because I like to collect useful functions and assume others do the same.
At any rate here is a script I came up with for someone a while back that can work with either 8 or 16 bit Lab documents. I do like how your version prints the X, Y position of the pixel. Mine just does a simple count.
function convertNumberToLab(channel,num,depth){
if(depth == 8 ){
if(channel == 0){
return Math.round((num/256)*100);
}else{
return num-128;
}
}
if(depth ==16){
if(channel == 0){
return Math.round(num*(32768/65535));
}else{
return Math.round((num-32768)/2);
}
}
}
File.prototype.readByte = function() {//aka unsigned byte
return this.read(1).charCodeAt(0);
};
File.prototype.readShort = function(ET) { //aka unsigned short, word = 2 bytes
var b1 = this.readByte();
var b2 = this.readByte();
if(ET == "LE"){// little endian
return (b2 << 8) + b1;
}else{// big endian - default
return (b1 << 8) + b2;
}
};
File.prototype.readFunction = function(bytes){
if(undefined == bytes) bytes = 1;
if(bytes == 2){
return this.readShort();
}else{
return this.readByte();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var rawFile = new File('~/Desktop/aaa.raw');// Lab mode
var csvFile = new File('~/Desktop/aaa.csv');
var bitDepth = 8;// 8 or 16 to match the saved bit depth
var bytes = bitDepth/8;
var len = 1;
rawFile.encoding = 'BINARY';
rawFile.open('r');
csvFile.open('w');
csvFile.writeln('Pixel #,L,a,b');
while(!rawFile.eof){
csvFile.write(convertNumberToLab(0,rawFile.readFunction(bytes), bitDepth)+',');
csvFile.write(convertNumberToLab(1,rawFile.readFunction(bytes), bitDepth)+',');
csvFile.writeln(convertNumberToLab(2,rawFile.readFunction(bytes), bitDepth));
len++;
}
rawFile.close();
csvFile.close();
Copy link to clipboard
Copied
That's fantastic Mike, I also like to collect as many different functions as I can and your maths are magnitudes better than mine!
Copy link to clipboard
Copied
Is there any way to extract the average LAB value of say 20 pixel squares?
I attempted to amend your script to this function, but realize that although the X and Y value change according to what i want, the LAB values are still running as per every pixel!
Copy link to clipboard
Copied
This will return a twenty pixel square Lab average...
//usage = getLabAverage X , Y
function main(){
if(!documents.length) return;
var Result =getLabAverage(10,10);
var L = Math.round(Result.lab.l);
var a = Math.round(Result.lab.a);
var b = Math.round(Result.lab.b);
alert("L = " + L + " a = " + a + " b = " + b);
}
main();function getLabAverage(x,y){
var startRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
activeDocument.selection.select([[x,y], [x+20,y], [x+20,y+20], [x, y+20]], SelectionType.REPLACE, 0, false);
activeDocument.activeLayer.applyAverage();
var solidColour = new SolidColor();
solidColour=app.activeDocument.colorSamplers.add([new UnitValue( x+1,'px'),new UnitValue(y+1,'px' )]).color;
executeAction( charIDToTypeID('undo'), undefined, DialogModes.NO );
activeDocument.selection.deselect();
app.preferences.rulerUnits = startRulerUnits;
app.activeDocument.colorSamplers.removeAll();
return solidColour;
}
Copy link to clipboard
Copied
Hi Paul,
Could you comment on what is wrong with how i amend the script? I am trying to make the script reads every 50 pixel and also the average of that point if possible.
I cant seem to integrate the script you provided into mine... Hope you can amend the following for me!
File.prototype.readByte = function() {
return this.read(1).charCodeAt(0);
};
function convertByteToSignedByte(num){
var dec = (num | (num % 256))-128
return dec;
};
function convertByteToLum( num){
var dec = Math.round((num/256)*100);
return dec;
};
function main(){
if(!documents.length) return;
if(activeDocument.mode != DocumentMode.LAB) {
alert("Please convert this document to Lab mode\r Then re-run this script");
return;
}
var Name = decodeURI(app.activeDocument.name).replace(/\.[^\.]+$/, '');
try{
var Path = activeDocument.path;
}catch(e){var Path = '~/Desktop';}
var rawFile = new File(Path +"/"+Name +".raw");
saveAsRaw(rawFile);
var csvFile = new File(Path +"/"+Name +".csv");
var W = activeDocument.width.as('px');
scan_pixel=50;
CountW=0;
CountH=0;
rawFile.encoding = 'BINARY';
rawFile.open('r');
csvFile.open('w');
csvFile.writeln('X,Y,L,a,b');
while(!rawFile.eof){
csvFile.write(CountW+',');
csvFile.write(CountH+',');
csvFile.write(convertByteToLum(rawFile.readByte())+',');
csvFile.write(convertByteToSignedByte(rawFile.readByte())+',');
csvFile.writeln(convertByteToSignedByte(rawFile.readByte()));
if(CountW >= 3776){
CountH=CountH+scan_pixel;
CountW=0;
}else if (CountH <=2520){
CountW=CountW+scan_pixel;
} else { return;}
}
csvFile.close();
}
main();
function saveAsRaw(file) {
var desc1 = new ActionDescriptor();
var desc2 = new ActionDescriptor();
desc2.putString( charIDToTypeID('FlCr'), "8BIM" );
desc2.putBoolean( charIDToTypeID('ChnI'), true );
desc1.putObject( charIDToTypeID('As '), charIDToTypeID('Rw '), desc2 );
desc1.putPath( charIDToTypeID('In '), new File( file ) );
desc1.putBoolean( charIDToTypeID('Cpy '), true );
executeAction( charIDToTypeID('save'), desc1, DialogModes.NO );
};
Copy link to clipboard
Copied
It would have to be done differently as an average needs to be done. The following script will do 50pixel squares and write the results to the CSV file...
main();
function main(){
if(!documents.length) return;
if(activeDocument.mode != DocumentMode.LAB) {
alert("Please convert this document to Lab mode\r Then re-run this script");
return;
}
var Name = decodeURI(app.activeDocument.name).replace(/\.[^\.]+$/, '');
try{
var Path = activeDocument.path;
}catch(e){var Path = '~/Desktop';}
var csvFile = new File(Path +"/"+Name +".csv");
csvFile.open('w');
csvFile.writeln("X,Y,L,a,b");
snapShot();
var startRulerUnits = preferences.rulerUnits;
preferences.rulerUnits = Units.PIXELS;
doc = app.activeDocument;
app.displayDialogs = DialogModes.NO;
doc.flatten();
var tilesAcross =activeDocument.width/50; //50 pixels
var tilesDown =activeDocument.height/50; //50 pixels
var tileWidth = parseInt(doc.width/tilesAcross);
var tileHeight = parseInt(doc.height/tilesDown);
ProcessFiles(tilesDown,tilesAcross,tileWidth,tileHeight);
revertToLastSnapshot();
app.preferences.rulerUnits = startRulerUnits;
csvFile.close();
function ProcessFiles(Down,Across,offsetX,offsetY,SaveFiles){
var solidColour = new SolidColor();
TLX = 0; TLY = 0; TRX = offsetX; TRY = 0;
BRX = offsetX; BRY = offsetY; BLX = 0; BLY = offsetY;
for(var a = 0; a < Down; a++){
for(var i = 0;i <Across; i++){
activeDocument.selection.select([[TLX,TLY],[TRX,TRY],[BRX,BRY],[BLX,BLY]], SelectionType.REPLACE, 0, false);
activeDocument.activeLayer.applyAverage();
getColour(TLX,TLY);
app.activeDocument.selection.deselect();
TLX = offsetX * (i+1) ; TRX = TLX + offsetX; BRX = TRX; BLX = TLX;
}
TLX = 0; TLY = offsetY * (a +1); TRX = offsetX; TRY = offsetY * (a +1);
BRX = offsetX; BRY = TRY + offsetY; BLX = 0; BLY = (offsetY * (a +1)+offsetY);
}
}
function getColour(X,Y){
solidColour=app.activeDocument.colorSamplers.add([new UnitValue( Number(X)+1,'px'),new UnitValue(Number(Y)+1,'px' )]).color;
var L = Math.round(solidColour.lab.l);
var a = Math.round(solidColour.lab.a);
var b = Math.round(solidColour.lab.b);
csvFile.writeln((Number(X)+1)+","+(Number(Y)+1)+","+L+","+a+",",b);
app.activeDocument.colorSamplers.removeAll();
}
function snapShot() {
var desc9 = new ActionDescriptor();
var ref5 = new ActionReference();
ref5.putClass( charIDToTypeID('SnpS') );
desc9.putReference( charIDToTypeID('null'), ref5 );
var ref6 = new ActionReference();
ref6.putProperty( charIDToTypeID('HstS'), charIDToTypeID('CrnH') );
desc9.putReference( charIDToTypeID('From'), ref6 );
desc9.putEnumerated( charIDToTypeID('Usng'), charIDToTypeID('HstS'), charIDToTypeID('FllD') );
executeAction( charIDToTypeID('Mk '), desc9, DialogModes.NO );
};
function revertToLastSnapshot() {
var doc = app.activeDocument;
var hsObj = doc.historyStates;
var hsLength = hsObj.length;
for (var i=hsLength - 1;i>-1;i--) {
if (hsObj.snapshot) {
doc.activeHistoryState = doc.historyStates.getByName('Snapshot ' + i);
break;
}
}
}
}
Copy link to clipboard
Copied
Hi Paul,
Thanks for your help. It works perfectly.
I realize that this method of using a color sampler and/or creation of tiles to get an average LAB color is actually much slower than the previous script which reads off the byte and converts it into a LAB pixel by pixel.
Thus, in your advice, if speed is the requirement, what other means would you explore?
Is there a way to direct the script to read off the byte at a certain X,Y coordinate?
The image that i am processing are often large resolution with 3776x2520pixels and it takes about 7-8mins to complete. Using the color sampler, at 50pixels spacing, it takes about 10mins.
My experience with the pixel by pixel CSV file is that it is often too huge to be opened properly.
Looking forward for your advice.
Copy link to clipboard
Copied
To get the average I don't think there is a faster way as the script has to select the region, apply the average filter to the 50x50pixel selection (2500 pixels). this would have to be done even if you wanted to read the values from the raw file.
I understand that the csv files must be HUGE, I think this script will read every 50th pixel (my maths are not that good!)...
File.prototype.readByte = function() {
return this.read(1).charCodeAt(0);
};
function convertByteToSignedByte(num){
var dec = (num | (num % 256))-128
return dec;
};
function convertByteToLum( num){
var dec = Math.round((num/256)*100);
return dec;
};
function main(){
readByteEvery = 50;//read every 50 bytes
if(!documents.length) return;
if(activeDocument.mode != DocumentMode.LAB) {
alert("Please convert this document to Lab mode\r Then re-run this script");
return;
}
var Name = decodeURI(app.activeDocument.name).replace(/\.[^\.]+$/, '');
try{
var Path = activeDocument.path;
}catch(e){var Path = '~/Desktop';}
var rawFile = new File(Path +"/"+Name +".raw");
saveAsRaw(rawFile);
var csvFile = new File(Path +"/"+Name +".csv");
var len = 0;
var W = activeDocument.width.as('px');
CountW=0;
CountH=0;
rawFile.encoding = 'BINARY';
rawFile.open('r');
csvFile.open('w');
csvFile.writeln('X,Y,L,a,b');
while(!rawFile.eof){
csvFile.write(CountW+',');
csvFile.write(CountH+',');
csvFile.write(convertByteToLum(rawFile.readByte())+',');
csvFile.write(convertByteToSignedByte(rawFile.readByte())+',');
csvFile.writeln(convertByteToSignedByte(rawFile.readByte()));
len++;
if(len % W == 0){
CountH++;
CountW=0;
}else{
CountW++;
}
for(var t =0;t< readByteEvery;t++){
rawFile.readByte();
rawFile.readByte();
rawFile.readByte();
len++;
if(len % W == 0){
CountH++;
CountW=0;
}else{
CountW++;
}
}}
rawFile.close();
csvFile.close();
rawFile.remove();
}
main();
function saveAsRaw(file) {
var desc1 = new ActionDescriptor();
var desc2 = new ActionDescriptor();
desc2.putString( charIDToTypeID('FlCr'), "8BIM" );
desc2.putBoolean( charIDToTypeID('ChnI'), true );
desc1.putObject( charIDToTypeID('As '), charIDToTypeID('Rw '), desc2 );
desc1.putPath( charIDToTypeID('In '), new File( file ) );
desc1.putBoolean( charIDToTypeID('Cpy '), true );
executeAction( charIDToTypeID('save'), desc1, DialogModes.NO );
};
Copy link to clipboard
Copied
Hi Paul,
Thanks for the advise. I guess i have to stick with this speed. It actually gets faster if i have a faster computer.
I've encountered another error using this script, to make a simple Crop Circle on a location where i identify as a sample point.
var idslct = charIDToTypeID( "slct" );
var desc215 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref80 = new ActionReference();
var idcustomShapeTool = stringIDToTypeID( "customShapeTool" );
ref80.putClass( idcustomShapeTool );
desc215.putReference( idnull, ref80 );
var iddontRecord = stringIDToTypeID( "dontRecord" );
desc215.putBoolean( iddontRecord, true );
var idforceNotify = stringIDToTypeID( "forceNotify" );
desc215.putBoolean( idforceNotify, true );
executeAction( idslct, desc215, DialogModes.NO );
// =======================================================
var idAddT = charIDToTypeID( "AddT" );
var desc204 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref75 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref75.putEnumerated( idPath, idOrdn, idTrgt );
desc204.putReference( idnull, ref75 );
var idT = charIDToTypeID( "T " );
var desc205 = new ActionDescriptor();
var idNm = charIDToTypeID( "Nm " );
desc205.putString( idNm, "Circle Thin Frame" );
var idTop = charIDToTypeID( "Top " );
var idPxl = charIDToTypeID( "#Pxl" );
desc205.putUnitDouble( idTop, idPxl, top_left_y );
var idLeft = charIDToTypeID( "Left" );
var idPxl = charIDToTypeID( "#Pxl" );
desc205.putUnitDouble( idLeft, idPxl, top_left_x );
var idBtom = charIDToTypeID( "Btom" );
var idPxl = charIDToTypeID( "#Pxl" );
desc205.putUnitDouble( idBtom, idPxl, bottom_right_y );
var idRght = charIDToTypeID( "Rght" );
var idPxl = charIDToTypeID( "#Pxl" );
desc205.putUnitDouble( idRght, idPxl, bottom_right_x);
var idcustomShape = stringIDToTypeID( "customShape" );
desc204.putObject( idT, idcustomShape, desc205 );
executeAction( idAddT, desc204, DialogModes.NO );
General Photoshop error encountered. The functionality may not be available in this version of Photoshop.
The ExtendScript actually show me the error as above.
However, i realize if i manually draw the first crop circle on the image, the 2nd crop circle will be successful by running the same script!
My aim is to create both a Crop circle at the point where i take the LAB sample and create a TEXT. However, i am facing problem with creating a TEXT too.
Hope you can help me out.
thanks!!!
Copy link to clipboard
Copied
If you just want to make a selection in might be better to use this function..
Circle(20,20,200,200);
function Circle(Top,Left,Bottom,Right,Feather) {
if(Feather == undefined) Feather = 0;
var desc3 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putProperty( charIDToTypeID('Chnl'), charIDToTypeID('fsel') );
desc3.putReference( charIDToTypeID('null'), ref1 );
var desc4 = new ActionDescriptor();
desc4.putUnitDouble( charIDToTypeID('Top '), charIDToTypeID('#Pxl'), Top );
desc4.putUnitDouble( charIDToTypeID('Left'), charIDToTypeID('#Pxl'), Left );
desc4.putUnitDouble( charIDToTypeID('Btom'), charIDToTypeID('#Pxl'), Bottom );
desc4.putUnitDouble( charIDToTypeID('Rght'), charIDToTypeID('#Pxl'), Right );
desc3.putObject( charIDToTypeID('T '), charIDToTypeID('Elps'), desc4 );
desc3.putUnitDouble( charIDToTypeID('Fthr'), charIDToTypeID('#Pxl'), Feather );
desc3.putBoolean( charIDToTypeID('AntA'), true );
executeAction( charIDToTypeID('setd'), desc3, DialogModes.NO );
};
What problem have you with the text layer and what details do you require to be entered for the text?
Copy link to clipboard
Copied
Hi Paul!
I managed to complete the Circle function you helped with. For the text, i am looking at writing a plain text, such as "Point 1 Sample" on say the top right hand corner of the circle that i previously created.
Hope this explains and appreciate the help!
regards,
Ryan
Copy link to clipboard
Copied
This might help, it does require that a selection is made so that it can work out where to put the text...
main();
function main(){
if(!documents.length) return;
var startRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
try{
var SB= activeDocument.selection.bounds;
}catch(e){
app.preferences.rulerUnits = startRulerUnits;
alert("There is no selection!");
return;
}
//Amend to suit
Percent = 10; //the text will 10 percent of the width of the document
var TextInfo = "Point 1 Sample";
var Black = new SolidColor();
Black.rgb.hexValue = '000000';
var newTextLayer = activeDocument.artLayers.add();
newTextLayer.kind = LayerKind.TEXT;
newTextLayer.textItem.kind = TextType.POINTTEXT;
newTextLayer.textItem.color = Black;
newTextLayer.textItem.font = "Georgia";
newTextLayer.textItem.size = 5;
newTextLayer.textItem.contents = TextInfo;
var myDoc = activeDocument;
var LB = myDoc.activeLayer.bounds;
var docHeight = myDoc.height;
var docWidth = myDoc.width;
var LHeight = Math.abs(LB[3].value) - Math.abs(LB[1].value);
var LWidth = Math.abs(LB[2].value) - Math.abs(LB[0].value);
var percentageWidth = ((docWidth/LWidth)*Percent);
myDoc.activeLayer.resize(percentageWidth,percentageWidth,AnchorPosition.MIDDLECENTER);
newTextLayer.textItem.position = Array(SB[2].value, SB[1].value);
app.preferences.rulerUnits = startRulerUnits;
}