Conditional BatchPlay values

Hello.

I’m not a Javascript expert. I’m trying to write a “resizeImage” function with the parameters "width, height, interpolationtype). In CEP, when I left either the “width” or the “height” value undefined, the image would scale proprtionally. In BatchPlay to do that, you would either have to use a line with the “width” value OR the “height” value. When you use both, the proportional scaling doesn’t work.

Here’s the code …

async function resizeImage(width, height, interpolationtype) {
const result = await batchPlay(
[
{
“_obj”: “imageSize”,
“width”: {"_unit": “pixelsUnit”, “_value”: width},
“height”: {"_unit": “pixelsUnit”, “_value”: height },
“interfaceIconFrameDimmed”: {
“_enum”: “interpolationType”,
“_value”: interpolationtype
},
“_isCommand”: false,
“_options”: {
“dialogOptions”: “dontDisplay”
}
}
],{
“synchronousExecution”: false,
“modalBehavior”: “fail”
});
}

Is there a way to either have the “width” line in there OR the “height” line in there, depending on if the parameter is assigned as “undefined” in the function or not? So the call “resizeImage(800, undefined, “bicubic”)” should scale the image to a width of 800px;

If you record in Alchemist and only change one value with constrain proportions on the it will give you this. It works. You can use either width or height. You just leave the other omitted.

async function resizeHeight(pix){

const batchPlay = require("photoshop").action.batchPlay;

const result = await batchPlay(
[
   {
      "_obj": "imageSize",
      "height": {
         "_unit": "pixelsUnit",
         "_value": pix
      },
      "scaleStyles": true,
      "constrainProportions": true,
      "interfaceIconFrameDimmed": {
         "_enum": "interpolationType",
         "_value": "automaticInterpolation"
      },
      "_isCommand": true,
      "_options": {
         "dialogOptions": "dontDisplay"
      }
   }
],{
   "synchronousExecution": false,
   "modalBehavior": "fail"
});

}
1 Like

Yeah. I’ve seen that. But I want to pass both values (width and height) in the parameters of the function and adapt the code inside the BatchPlay routine accordingly.

If you pass both values then you are controlling the aspect ratio so constrain proportions doesn’t apply. Say for example you have an image that is 2400x3000. That is a 4:5 aspect ratio. Now say you pass 1000x1500 for the resize. That is a 2:3 aspect ratio. You have just changed the aspect ratio. If you already are controlling the aspect ration the you don’t need the constrain proportion.

It works the same way in the Photoshop UI. With the constrain proportion on, you can only control one value in the UI. As soon as you change one value, Photoshop controls the other automatically.

How I do it is have 2 functions, one for width and the other for height. I just use the one I need based on whether I want to give it width or height. It works fine.

I suppose you could cram it all in one function and put some other controls in it for do that correct thing but not sure I understand the need for that.

1 Like

If you need one function you can do something like this. Then you give it the pixels dimensions for one side and tell it whether that is width or height. then it auto calculates the other side based on the correct aspect ratio.

async function resizeImage(pix,side){

const batchPlay = require("photoshop").action.batchPlay;

if(side=="width"){

const result = await batchPlay(
[
   {
      "_obj": "imageSize",
      "width": {
         "_unit": "pixelsUnit",
         "_value": pix
      },
      "scaleStyles": true,
      "constrainProportions": true,
      "interfaceIconFrameDimmed": {
         "_enum": "interpolationType",
         "_value": "automaticInterpolation"
      },
      "_isCommand": true,
      "_options": {
         "dialogOptions": "dontDisplay"
      }
   }
],{
   "synchronousExecution": false,
   "modalBehavior": "fail"
});
    
}

else {

const result = await batchPlay(
[
   {
      "_obj": "imageSize",
      "height": {
         "_unit": "pixelsUnit",
         "_value": pix
      },
      "scaleStyles": true,
      "constrainProportions": true,
      "interfaceIconFrameDimmed": {
         "_enum": "interpolationType",
         "_value": "automaticInterpolation"
      },
      "_isCommand": true,
      "_options": {
         "dialogOptions": "dontDisplay"
      }
   }
],{
   "synchronousExecution": false,
   "modalBehavior": "fail"
});
    
}

}

For now I’m also using two functions, but it’s not the most elegant way and CEP handled it rather nicely. There I could type … resizeImage (undefined, “800 px”, 72, ResampleMethod.BICUBIC ) or resizeImage (“1000 px”, undefined, 72, ResampleMethod.BICUBIC ) and the function knew what to do without having two separate functions.

Aside from this specific function, a solution to adapt the BatchPlay code conditionally might come in handy in other places as well. So this is more of a Javascript issue overall.

The function I posted should work. It could be condensed quite a bit. That was straight out of Alchemist and just shoved into an if/then.

1 Like

I managed to get it done by using your idea and two complete BatchPlay blocks. It would have been nicer and cleaner if I could have just changed the two affected lines with the width and height paramaters. But I guess that’s not possible. As I said - I’m a Javascript noob.

Here’s the complete function (which should exist in the API anyway) …

// RESIZE IMAGE NEW
app.Document.prototype.resizeImage = async function(width, height, interpolationtype) {

if (width) {
const result = await batchPlay(
[
{
“_obj”: “imageSize”,
“width”: {"_unit": “pixelsUnit”, “_value”: width},
“constrainProportions”: true,
“interfaceIconFrameDimmed”: {
“_enum”: “interpolationType”,
“_value”: interpolationtype
},
“_isCommand”: false,
“_options”: {
“dialogOptions”: “dontDisplay”
}
}
],{
“synchronousExecution”: false,
“modalBehavior”: “fail”
});
} else {
const result = await batchPlay(
[
{
“_obj”: “imageSize”,
“height”: {"_unit": “pixelsUnit”, “_value”: height},
“constrainProportions”: true,
“interfaceIconFrameDimmed”: {
“_enum”: “interpolationType”,
“_value”: interpolationtype
},
“_isCommand”: false,
“_options”: {
“dialogOptions”: “dontDisplay”
}
}
],{
“synchronousExecution”: false,
“modalBehavior”: “fail”
});
}
}

It is possible. The (Action)Descriptors that batchPlay takes as parameters are simply Javascript objects.
So what you want to do is conditionally add or don’t add the width or height property to said object.
The easiest way to do this is by using the spread syntax paired with a ternary operator, for example

{
  // some object
  ...(condition ? {someProperty: 'someValue'} : {})
}

So for the imageSize function it would look something like this:

function __imageSize(width?: number, height?: number, _unit: Unit = 'pixelsUnit', 
scaleStyles: boolean = true, constrainProportions: boolean = true, interpolation: InterpolationType = 'automaticInterpolation', noise?: number) {
  return {
    _obj: 'imageSize',
    ...(width ? {
      width: {
        _unit,
        _value: width,
      },
    } : {}),
    ...(height ? {
      height: {
        _unit,
        _value: height,
      },
    } : {}),    
    scaleStyles,
    constrainProportions,
    interfaceIconFrameDimmed: {
      _enum: 'interpolationType',
      _value: interpolation,
    },
    ...(interpolation === 'preserveDetailsUpscale' && noise? {noise} : {}),
  }
}

The functions returns the Descriptor object, which u can then play with BatchPlay.

2 Likes

Thanks a lot for your insight. I tried a simpler, inline version, but can’t get it to work. Would something like this be possible, or do I have to use the exact syntax that you’ve used.

// RESIZE IMAGE NEW
app.Document.prototype.resizeImage = async function(width, height, interpolationtype) {
const result = await batchPlay(
[
{
“_obj”: “imageSize”,
…(width ? {“width”: {"_unit": “pixelsUnit”, “_value”: width}} : {}),
…(height ? {“height”: {"_unit": “pixelsUnit”, “_value”: height}} : {}),
“constrainProportions”: true,
“interfaceIconFrameDimmed”: {
“_enum”: “interpolationType”,
“_value”: interpolationtype
},
“_isCommand”: false,
“_options”: {
“dialogOptions”: “dontDisplay”
}
}
],{
“synchronousExecution”: false,
“modalBehavior”: “fail”
});
}

Looks fine at first glance. Why did you add the _isCommand: false though?
Also, if you use code highlighting for the code you paste here it becomes much easier to read.

1 Like

Somehow I get an error. The “isCommand” is just there from copying from Alchemist. I’ll check the function again later. Maybe just some stupid bracket issue.

Try without the type definitions (those are for TypeScript):

Alternatively if the ... and ? syntax is confusing (which it can be, especially one hasn’t been immersed in the recent JS standards improvements), you can also do it this way (remember that these are just JS objects, and can thus be modified like any other object.)

async function resizeImage(width, height) {
  let imageSizeDescriptor = {
    "_obj": "imageSize",
    "scaleStyles": true,
    "constrainProportions": true,
    "interfaceIconFrameDimmed": {
      "_enum": "interpolationType",
      "_value": "automaticInterpolation"
    },
    "_options": {
      "dialogOptions": "dontDisplay"
    }
  };

  if (width !== undefined && width !== null) {
    imageSizeDescriptor.width = {
      "_unit": "pixelsUnit",
      "_value": width
    };
  }
  if (height !== undefined && height !== null) {
    imageSizeDescriptor.height = {
      "_unit": "pixelsUnit",
      "_value": height
    };
  }

  return batchPlay([ imageSizeDescriptor ], {
    "synchronousExecution": false,
    "modalBehavior": "fail"
  });
}
2 Likes

Thanks for the examples. These are really helpful.