Skip to content

Commit 59ac4cd

Browse files
committed
selections working
1 parent cf6eb16 commit 59ac4cd

File tree

8 files changed

+232
-75
lines changed

8 files changed

+232
-75
lines changed

app/app.js

Lines changed: 169 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,148 @@
1919
arrayAccessFormPaths: ['xmpmeta.RDF.Description.RecommendedFrames.Bag.li']
2020
};
2121
})
22-
.controller('MainController', ['$scope', 'csInterface', 'rmdBridge', 'rmdDefault', 'lodash',
23-
function ($scope, csInterface, RMD, rmdDefault, _) {
22+
.controller('MainController', ['$scope', 'csInterface', 'rmdBridge',
23+
'rmdDefault', 'lodash', 'psEvent',
24+
function ($scope, csInterface, RMD, rmdDefault, _, psEvent) {
25+
26+
var activeArea = null;
27+
var gExtensionID = csInterface.getExtensionID();
28+
29+
// Tell Photoshop to not unload us when closed
30+
function Persistent(inOn) {
31+
gStartDate = new Date();
32+
var event;
33+
if (inOn) {
34+
event = new CSEvent("com.adobe.PhotoshopPersistent", "APPLICATION");
35+
} else {
36+
event = new CSEvent("com.adobe.PhotoshopUnPersistent", "APPLICATION");
37+
}
38+
event.extensionId = gExtensionID;
39+
csInterface.dispatchEvent(event);
40+
SetResultTime();
41+
}
42+
// Tell Photoshop the events we want to listen for
43+
var _register = function(inOn, inEvents) {
44+
var event;
45+
if (inOn) {
46+
event = new CSEvent("com.adobe.PhotoshopRegisterEvent", "APPLICATION");
47+
} else {
48+
event = new CSEvent("com.adobe.PhotoshopUnRegisterEvent", "APPLICATION");
49+
}
50+
event.extensionId = gExtensionID;
51+
event.data = inEvents;
52+
console.log('dispatching event: ', event);
53+
csInterface.dispatchEvent(event);
54+
};
55+
56+
/**
57+
* Toggle the 'set' event (Area mark) listener.
58+
* @param status - True or false -> on or off.
59+
*/
60+
var selectListener = function(status) {
61+
_register(status, psEvent.set.toString());
62+
};
63+
// deactivate leftover listeners on startup
64+
selectListener(false);
2465

25-
$scope.greeting = 'Hallo';
2666
$scope.rmd = RMD.xmp.xmpmeta.RDF.Description;
67+
$scope.documentSize = {};
2768

2869
// TODO: Remove debugging exposures.
2970
global._scope = $scope;
3071
global.RMD = RMD;
3172

73+
// store the original image size
74+
csInterface.evalScript('getDocumentSize()', function(value) {
75+
$scope.documentSize = JSON.parse(value);
76+
});
77+
78+
79+
/**
80+
* Event callback for Photoshop Events. Currently only the 'set' event.
81+
* @param csEvent Photoshop Event.
82+
*/
83+
var PhotoshopCallbackUnique = function(csEvent) {
84+
// console.log('receiving Callback: ', csEvent);
85+
if (typeof csEvent.data === "string") {
86+
var eventData = csEvent.data.replace("ver1,{", "{");
87+
var data = JSON.parse(eventData);
88+
console.log(data);
89+
if(data.eventData.null._property === 'selection' && data.eventID === psEvent.set
90+
&& data.eventData.to._obj === 'rectangle' && activeArea){
91+
if(data.eventData.to.top._unit === 'pixelsUnit'){
92+
setAreaValues(data.eventData.to);
93+
} else {
94+
alert('Please set the units to Pixels. Other units are currently not supported.');
95+
}
96+
}
97+
} else {
98+
console.error("PhotoshopCallbackUnique expecting string for csEvent.data!");
99+
}
100+
};
101+
102+
/**
103+
* Get the RMD object node for the given area key.
104+
* @returns {*} RMD node.
105+
*/
106+
var getNodeForActiveArea = function() {
107+
if(activeArea === 'default') {
108+
return $scope.rmd.CropArea;
109+
} else if (activeArea === 'safe') {
110+
return $scope.rmd.SafeArea;
111+
} else {
112+
return $scope.rmd.RecommendedFrames.Bag.li[activeArea]
113+
}
114+
};
115+
116+
/**
117+
* Sets the values returned by a Photoshop event in the RMD node.
118+
* @param ps_data Photoshop event property.
119+
*/
120+
var setAreaValues = function(ps_data) {
121+
var node, x, y, width, height;
122+
node = getNodeForActiveArea();
123+
// PS returns the are as left, top, width height.
124+
// The RMD standard requires centerX, centerY, width height.
125+
width = ps_data.right._value - ps_data.left._value;
126+
height = ps_data.bottom._value - ps_data.top._value;
127+
x = ps_data.left._value + width/2;
128+
y = ps_data.top._value + height/2;
129+
node.x.__text = (x / $scope.documentSize.width).toString();
130+
node.y.__text = (y / $scope.documentSize.height).toString();
131+
node.w.__text = (width / $scope.documentSize.width).toString();
132+
node.h.__text = (height / $scope.documentSize.height).toString();
133+
$scope.$apply();
134+
};
135+
136+
var setSelectionFromRmd = function() {
137+
var x, y, width, height, left, top, coords;
138+
var node = getNodeForActiveArea();
139+
x = parseFloat(node.x.__text) * $scope.documentSize.width;
140+
y = parseFloat(node.y.__text) * $scope.documentSize.height;
141+
width = parseFloat(node.w.__text) * $scope.documentSize.width;
142+
height = parseFloat(node.h.__text) * $scope.documentSize.height;
143+
left = parseInt(x - width/2);
144+
top = parseInt(y - height/2);
145+
146+
coords = {
147+
left: left,
148+
top: top,
149+
right: parseInt(left + width),
150+
bottom: parseInt(top + height)
151+
};
152+
csInterface.evalScript('makeSelection(' + JSON.stringify(coords) + ')');
153+
};
154+
155+
// all callbacks need to be unique so only your panel gets them
156+
// for Photoshop specific add on the id of your extension
157+
csInterface.addEventListener("com.adobe.PhotoshopJSONCallback" + gExtensionID, PhotoshopCallbackUnique);
158+
159+
/**
160+
* Adds a new crop region to the metadata. There are three types: The default crop area,
161+
* the safety area and a list of recommended crop areas
162+
* @param name - the name of the area. One of 'default', 'safe' or a number.
163+
*/
32164
$scope.addCropArea = function(name) {
33165
if(name === undefined) {
34166
$scope.rmd.RecommendedFrames.Bag.li.push(_.cloneDeep(rmdDefault.xmpmeta.RDF.Description.SafeArea));
@@ -38,7 +170,15 @@
38170
$scope.rmd.SafeArea = _.cloneDeep(rmdDefault.xmpmeta.RDF.Description.SafeArea);
39171
}
40172
};
173+
/**
174+
* Removes the crop area from the metadata.
175+
* @param index - the name of the area to remove. One of 'default', 'safe' or a number.
176+
*/
41177
$scope.removeCropArea = function(index) {
178+
// deactivate area
179+
if(activeArea === index) {
180+
$scope.setActiveArea(null);
181+
}
42182
if(index === 'default') {
43183
delete $scope.rmd.CropArea;
44184
} else if(index === 'safe') {
@@ -47,6 +187,32 @@
47187
$scope.rmd.RecommendedFrames.Bag.li.splice(index, 1);
48188
}
49189
};
190+
/**
191+
* Check if the given area is active (listening to events)
192+
* @param area - the name of the area.
193+
* @returns {boolean}
194+
*/
195+
$scope.isAreaActive = function(area) {
196+
return area === activeArea;
197+
};
198+
/**
199+
* Sets the given area as active.
200+
* @param area - the name of the area.
201+
*/
202+
$scope.setActiveArea = function(area) {
203+
csInterface.evalScript('clearSelection()');
204+
if(activeArea === area || area === null) {
205+
// turn it off
206+
selectListener(false);
207+
activeArea = null;
208+
} else {
209+
if(!activeArea) {
210+
selectListener(true);
211+
}
212+
activeArea = area;
213+
setSelectionFromRmd();
214+
}
215+
}
50216

51217
}
52218
]);

app/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ angular.module('app')
88
})
99
.constant('xmlNamespaces', {
1010
xmp: 'http://ns.adobe.com/xap/1.0/',
11-
rmd: 'http://universalimages.github.io/rmd/',
11+
rmd: 'http://universalimages.github.io/rmd/0.1/',
1212
stDim: 'http://ns.adobe.com/xap/1.0/sType/Dimensions#',
1313
stArea: 'http://ns.adobe.com/xmp/sType/Area#',
1414
exif: 'http://ns.adobe.com/exif/1.0/',

app/services/crop_area.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ angular.module('app')
22
.directive('cropAreas', function(){
33
return {
44
restrict: 'E',
5-
templateUrl: '../templates/crop_areas.html',
6-
link: function(scope, element, attr) {
7-
}
5+
templateUrl: '../templates/crop_areas.html'
86
};
97
})
108

@@ -42,10 +40,9 @@ angular.module('app')
4240
title: '@',
4341
struct: '=struct',
4442
removeFn: '=',
45-
index: '@'
46-
},
47-
link: function(scope, element, attr) {
48-
43+
activateFn: '=',
44+
index: '@',
45+
isActive: '='
4946
}
5047
};
5148
}])

app/services/csinterface.js

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,9 @@
11
(function(){
22

3-
function PhotoshopCallbackUnique(csEvent) {
4-
try {
5-
if (typeof csEvent.data === "string") {
6-
var eventData = csEvent.data.replace("ver1,{", "{");
7-
var eventDataParse = JSON.parse(eventData);
8-
var jsonStringBack = JSON.stringify(eventDataParse);
9-
10-
console.log(eventDataParse);
11-
//SetResultLabel("PhotoshopCallbackUnique: " + jsonStringBack);
12-
//JSLogIt("PhotoshopCallbackUnique: " + jsonStringBack);
13-
//
14-
//var uiItemToUpdate = null;
15-
//if (eventDataParse.eventID === eventMake)
16-
// uiItemToUpdate = lblMake;
17-
//else if (eventDataParse.eventID === eventDelete)
18-
// uiItemToUpdate = lblDelete;
19-
//else if (eventDataParse.eventID === eventClose)
20-
// uiItemToUpdate = lblClose;
21-
//else if (eventDataParse.eventID === eventSelect)
22-
// uiItemToUpdate = lblSelect;
23-
//else if (eventDataParse.eventID === eventSet)
24-
// uiItemToUpdate = lblSet;
25-
//
26-
//if (uiItemToUpdate !== null) {
27-
// var count = Number(uiItemToUpdate.innerHTML) + 1;
28-
// uiItemToUpdate.innerHTML = " " + count;
29-
//}
30-
//
31-
//// if you just made a text layer, let me check my object for something
32-
//// interesting to dump to log
33-
//if (eventDataParse &&
34-
// eventDataParse.eventData.null &&
35-
// eventDataParse.eventData.null._ref &&
36-
// eventDataParse.eventData.null._ref === "textLayer") {
37-
// JSLogIt("Got a text layer, trying to find paragraphStyleRange");
38-
// if (eventDataParse.eventData.using &&
39-
// eventDataParse.eventData.using.paragraphStyleRange) {
40-
// JSLogIt("paragraphStyleRange:" + eventDataParse.eventData.using.paragraphStyleRange);
41-
// JSLogIt("paragraphStyleRange typeof :" + typeof eventDataParse.eventData.using.paragraphStyleRange);
42-
// JSLogIt("paragraphStyleRange[0].from: " + eventDataParse.eventData.using.paragraphStyleRange[0].from);
43-
// }
44-
//}
45-
} else {
46-
console.error("PhotoshopCallbackUnique expecting string for csEvent.data!");
47-
}
48-
} catch(e) {
49-
console.error("PhotoshopCallbackUnique catch:" + e);
50-
}
51-
}
52-
53-
543
angular.module('app')
554
.factory('csInterface', function(){
565
var csInterface = new CSInterface();
57-
var gExtensionID = csInterface.getExtensionID();
586

59-
// all callbacks need to be unique so only your panel gets them
60-
// for Photoshop specific add on the id of your extension
61-
csInterface.addEventListener("com.adobe.PhotoshopJSONCallback" + gExtensionID, PhotoshopCallbackUnique);
62-
console.log('Adding Event Listener for ', gExtensionID);
637
return csInterface;
648
});
659

jsx/hostscript.jsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,47 @@
11
/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
22
/*global $, Folder*/
33

4+
/* jshint ignore:start */
5+
/**
6+
* JSON - from: https://github.com/douglascrockford/JSON-js
7+
*/
8+
if(typeof JSON!=='object'){JSON={};}(function(){'use strict';function f(n){return n<10?'0'+n:n;}function this_value(){return this.valueOf();}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z':null;};Boolean.prototype.toJSON=this_value;Number.prototype.toJSON=this_value;String.prototype.toJSON=this_value;}var cx,escapable,gap,indent,meta,rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);})+'"':'"'+string+'"';}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}if(typeof rep==='function'){value=rep.call(holder,key,value);}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;}if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==='string'){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}if(typeof JSON.stringify!=='function'){escapable=/[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}return str('',{'':value});};}if(typeof JSON.parse!=='function'){cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}return reviver.call(holder,key,value);}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}throw new SyntaxError('JSON.parse');};}}());
9+
/**
10+
* Array.forEach - from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
11+
*/
12+
Array.prototype.forEach||(Array.prototype.forEach=function(r,t){var o,n;if(null==this)throw new TypeError(" this is null or not defined");var e=Object(this),i=e.length>>>0;if("function"!=typeof r)throw new TypeError(r+" is not a function");for(arguments.length>1&&(o=t),n=0;i>n;){var a;n in e&&(a=e[n],r.call(o,a,n,e)),n++}});
13+
/* jshint ignore:end */
14+
15+
function getDocumentSize(){
16+
// Set photoshop preferences to use pixel units.
17+
var startRulerUnits = app.preferences.rulerUnits;
18+
var startTypeUnits = app.preferences.typeUnits;
19+
app.preferences.rulerUnits = Units.PIXELS;
20+
app.preferences.typeUnits = TypeUnits.PIXELS;
21+
22+
var size = {width: app.activeDocument.width.value, height: app.activeDocument.height.value};
23+
24+
// Reset the application preferences
25+
app.preferences.rulerUnits = startRulerUnits;
26+
app.preferences.typeUnits = startTypeUnits;
27+
return JSON.stringify(size);
28+
}
29+
30+
function clearSelection() {
31+
app.activeDocument.selection.deselect();
32+
}
433

5-
function sayHello() {
6-
alert("hello from ExtendScript");
34+
/**
35+
* Selects the given region.
36+
* @param region - Array of four coordinates: left, top, right, bottom
37+
*/
38+
function makeSelection(r) {
39+
// LogIt('Region: ' + JSON.stringify(region) + ', ' + typeof region);
40+
var region = [
41+
[r.left, r.top], [r.right, r.top], [r.right, r.bottom],
42+
[r.left, r.bottom], [r.left, r.top]
43+
];
44+
app.activeDocument.selection.select(region);
745
}
846

947

@@ -124,6 +162,13 @@ $.delegates = (function (exports) {
124162
// public ---
125163

126164
this.open = function () {
165+
// This prototype currently only supports one open document.
166+
if (app.documents.length > 1) {
167+
alert('Only one document can be open.');
168+
throw new Error('Only one document can be open.');
169+
}
170+
171+
127172
var target = accessor.getTarget ? accessor.getTarget() : findOrCreateDocument();
128173
// if no target could be retrieved, we don't expose the API.
129174
if (!target) return;

root/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ <h1>Universal Images</h1>
1818

1919
<div id="rmd-container" xmp-extract>
2020
Target name: {{targetName}}<br>
21-
Image Dimensions: {{xDimension}}x{{yDimension}}<br>
21+
Image Dimensions: {{documentSize.width}}x{{documentSize.height}}<br>
2222

2323
<fieldset>
2424
<legend>Allowed Derivates</legend>

templates/crop_area.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<div class="crop-area">
2-
<button class="topcoat-icon-button--quiet">
3-
<span class='icomatic icon'>rectangleoutline</span> {{title}}
2+
<button class="topcoat-icon-button--quiet" ng-click="activateFn(index)">
3+
<span class='icomatic icon' ng-show="isActive(index)">roundedrectangle</span>
4+
<span class='icomatic icon' ng-hide="isActive(index)">roundedrectangleoutline</span>
5+
{{title}}
46
</button>
57
<input type="text" class="topcoat-text-input topcoat-text-input--small" ng-model="struct.x.__text" smart-float />
68
<input type="text" class="topcoat-text-input topcoat-text-input--small" ng-model="struct.y.__text" smart-float />

0 commit comments

Comments
 (0)