Help needed with simple color adjustment plugin

Hi, I’m trying to build a simple plugin that takes the active layer and applies a color balance with values taken from a simple UI with sliders, but I just can’t make it work. So I dived into the docs and my headache didn’t get any better… I keep on getting errors or just plain nothing out of it. The UI is mostly done and working… it’s just the actual JS code that sucks! Should I use batchPlay or some other dark magic? Any help will be more than appreciated! :slight_smile:

Check out @Jarda’s Alchemist plugin. Specifically the inspector how-to video

2 Likes

Thanks for the info! I’m already using Alchimist, but I need some more tutorials in order to get the right workflow since wild copy-paste seems not a good idea! :wink:

Well… With Alchemist you can get the BatchPlay code to set color balance values. Just replace these values with the ones from your sliders, update the target layer and you’re pretty much done.

1 Like

You might find this thread of some help.
Whilst it doesn’t address your particular use case it does demonstrate a high-level approach to structuring your code.

3 Likes

That’s exactly what I did, but I must have screwed something somewhere LOL! Gonna check it all again and see what happens.

Thanks mate! Gonna have a look! :slight_smile:

1 Like

Don’t be shy, post your code!

Haha not shy at all, I was AFK for a while! :wink:

Here it’s the mess I made (with the help of AI, a bit hit’n’miss and lots of unused stuff in there - since I made some experiments around the main concept).

The final plugin I have in mind is way more complex, but I’d like to break it down to smaller actions first - and see how those can be combined later (I will put a draft on paper of the final design document of course ).

const photoshop = require("photoshop");
const app = photoshop.app;
//const { batchPlay } = require("photoshop", "uxp");
const { entrypoints } = require("uxp");

const {executeAsModal} = require("photoshop").core;
const {batchPlay} = require("photoshop").action;

const btnPopulate = document.getElementById("btnPopulate");
const columnSwitch = document.querySelector("sp-switch[label='ColumnSwitch']");
const rowSwitch = document.querySelector("sp-switch[label='RowSwitch']");
const cyanRedInputCol = document.getElementById("CyanRedInputCol");
const magentaGreenInputCol = document.getElementById("MagentaGreenInputCol");
const yellowBlueInputCol = document.getElementById("YellowBlueInputCol");
const cyanRedSliderCol = document.getElementById("CyanRedSliderCol");
const magentaGreenSliderCol = document.getElementById("MagentaGreenSliderCol");
const yellowBlueSliderCol = document.getElementById("YellowBlueSliderCol");
const toneBalanceRadiosCol = document.querySelectorAll("sp-radio[name='radioCol']");
const preserveLumaCol = document.getElementById("PreserveLumaCol");
const saturationSliderCol = document.getElementById("SaturationSliderCol");
const brightnessSliderCol = document.getElementById("BrightnessSliderCol");
const contrastSliderCol = document.getElementById("ContrastSliderCol");
const cyanRedInputRow = document.getElementById("CyanRedInputRow");
const magentaGreenInputRow = document.getElementById("MagentaGreenInputRow");
const yellowBlueInputRow = document.getElementById("YellowBlueInputRow");
const cyanRedSliderRow = document.getElementById("CyanRedSliderRow");
const magentaGreenSliderRow = document.getElementById("MagentaGreenSliderRow");
const yellowBlueSliderRow = document.getElementById("YellowBlueSliderRow");
const toneBalanceRadiosRow = document.querySelectorAll("sp-radio[name='radioRow']");
const preserveLumaRow = document.getElementById("PreserveLumaRow");
const saturationSliderRow = document.getElementById("SaturationSliderRow");
const brightnessSliderRow = document.getElementById("BrightnessSliderRow");
const contrastSliderRow = document.getElementById("ContrastSliderRow");

 // Add event listeners for UI elements
 btnPopulate.addEventListener("click", function () {
  RunAdjuster();
});

// Event listener for the slider
cyanRedSliderCol.addEventListener("input", function () {
  const sliderValue = parseFloat(cyanRedSliderCol.value);
  // Update the input field with the slider value
  cyanRedInputCol.value = sliderValue;
//  console.log("New value:", parseFloat(cyanRedInputCol.value));

});

 // Event listener for the input field
 cyanRedInputCol.addEventListener("input", function () {
  const inputValue = parseFloat(cyanRedInputCol.value);
  // Update the slider with the input field value
  cyanRedSliderCol.value = inputValue;
});

/* // Wait for the DOM to be ready
document.addEventListener("DOMContentLoaded", function () {
  // Reference to various UI elements

});

/* ENGINE : BELOW */


function RunAdjuster() {
  // Get the currently selected layer in Photoshop
  const selectedLayer = app.activeDocument.activeLayers[0];

  console.log("selectedLayer:", selectedLayer);  // Add this line to log the selected layer
  console.log("Is pixel layer:", selectedLayer.kind);  // Check if it's a pixel layer

  // Ensure a layer is selected and it's a pixel layer
  if (selectedLayer && selectedLayer.kind) {
   
      const cyanRedSliderColValue = parseFloat(cyanRedSliderCol.value);
      const magentaGreenSliderColValue = parseFloat(magentaGreenSliderCol.value);
      const yellowBlueSliderColValue = parseFloat(yellowBlueSliderCol.value);
      const preserveLumaColValue = preserveLumaCol.checked;

      // Define the color adjustments using values from the UI elements
      const colorBalanceAdjustment = {
        _obj: "colorBalance",
        shadowLevels: [cyanRedSliderColValue, magentaGreenSliderColValue, yellowBlueSliderColValue],
        midtoneLevels: [cyanRedSliderColValue, magentaGreenSliderColValue, yellowBlueSliderColValue],
        highlightLevels: [cyanRedSliderColValue, magentaGreenSliderColValue, yellowBlueSliderColValue],
        preserveLuminosity: preserveLumaColValue,
      };

      // Apply the color adjustments to the selected pixel layer
      //await selectedLayer.editLayer({
      //  adjustments: [colorBalanceAdjustment],
      //});

     
      try {
          // Log the colorBalanceAdjustment object before applying

          console.log("colorBalanceAdjustment:", colorBalanceAdjustment);

        // Apply the color adjustments using batchPlay

       
        const result = photoshop.action.batchPlay(
          [
            {
              "_obj": "applyAdjustment",
              "to": {
                "_ref": selectedLayer,
                "_enum": "ordinal",
                "_value": "targetEnum"
              },
              "using": {
                "_obj": "colorBalance",
                "colorBalance": colorBalanceAdjustment,
                "globalAngle": 0,
              },
            },
          ],
          {synchronousExecution: true}
        );
       


        console.log("Adjustments applied:", result);
        console.log("colorBalanceAdjustment:", colorBalanceAdjustment);
       
      } catch (error) {
        console.error("Error applying adjustments:", error);
        alert("An error occurred while applying color adjustments.");
      }

  } else {
    alert("Please select a pixel layer to apply color adjustments.");
  }

}


/** */

entrypoints.setup({
  commands: {
    // Define your commands here
  },
  panels: {
    // Define your panels here
  },
});

So, what’s not executing that you’re expecting to?

At first glance all I can spot is {synchronousExecution: true} as your second argument in your batchPlay command, change that to an empty Object {}.
That said, I’m sitting in the park and I’m reading it on my phone

I always get this error:

Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: Error: NAPI API failure: String expected
code: "2000"
number: 2000
message: "NAPI API failure: String expected"
stack: "Error: NAPI API failure: String expected"

This might likely be the culprit:

The function batchPlay is an asynchronous function which returns a promise. You have to put in an await before in order to wait for the proper (resolved) result. Subsequent functions may depend on that result and will complain if they get a promise instead of something else they expect (e.g. a string).

P.S. Fixing that also means that the enclosing function “RunAdjuster” will have to be asynchronous and will have to be declared as such.

2 Likes

One or some of the values you’re passing is not a string, when it’s expected to be. Maybe you’re passing a number or a null, but it has to be a string. Check your setters and BP descriptor values

1 Like

Thanks for the hints mates, gonna have a look into that!
(Yeah it’s always me, just replying from my work account! :wink: )

So I’m back on this plugin (after a week of python and native windows development :frowning: )

Still can’t make it work… I just can’t pass values to color balance thingie! :smiley:
Updated code below.

const photoshop = require("photoshop");
const app = photoshop.app;
const { entrypoints } = require("uxp");

const btnPopulate = document.getElementById("btnPopulate");
const columnSwitch = document.querySelector("sp-switch[label='ColumnSwitch']");
const rowSwitch = document.querySelector("sp-switch[label='RowSwitch']");
const cyanRedInputCol = document.getElementById("CyanRedInputCol");
const magentaGreenInputCol = document.getElementById("MagentaGreenInputCol");
const yellowBlueInputCol = document.getElementById("YellowBlueInputCol");
const cyanRedSliderCol = document.getElementById("CyanRedSliderCol");
const magentaGreenSliderCol = document.getElementById("MagentaGreenSliderCol");
const yellowBlueSliderCol = document.getElementById("YellowBlueSliderCol");
const toneBalanceRadiosCol = document.querySelectorAll("sp-radio[name='radioCol']");
const preserveLumaCol = document.getElementById("PreserveLumaCol");
const saturationSliderCol = document.getElementById("SaturationSliderCol");
const brightnessSliderCol = document.getElementById("BrightnessSliderCol");
const contrastSliderCol = document.getElementById("ContrastSliderCol");
const cyanRedInputRow = document.getElementById("CyanRedInputRow");
const magentaGreenInputRow = document.getElementById("MagentaGreenInputRow");
const yellowBlueInputRow = document.getElementById("YellowBlueInputRow");
const cyanRedSliderRow = document.getElementById("CyanRedSliderRow");
const magentaGreenSliderRow = document.getElementById("MagentaGreenSliderRow");
const yellowBlueSliderRow = document.getElementById("YellowBlueSliderRow");
const toneBalanceRadiosRow = document.querySelectorAll("sp-radio[name='radioRow']");
const preserveLumaRow = document.getElementById("PreserveLumaRow");
const saturationSliderRow = document.getElementById("SaturationSliderRow");
const brightnessSliderRow = document.getElementById("BrightnessSliderRow");
const contrastSliderRow = document.getElementById("ContrastSliderRow");

// Add event listeners for UI elements
btnPopulate.addEventListener("click", function () {
  RunAdjuster();
});

// Event listener for the slider
cyanRedSliderCol.addEventListener("input", function () {
  const sliderValue = parseFloat(cyanRedSliderCol.value);
  // Update the input field with the slider value
  cyanRedInputCol.value = sliderValue;
});

// Event listener for the input field
cyanRedInputCol.addEventListener("input", function () {
  const inputValue = parseFloat(cyanRedInputCol.value);
  // Update the slider with the input field value
  cyanRedSliderCol.value = inputValue;
});

async function RunAdjuster() { 
// Get the currently selected layer in Photoshop
  const selectedLayer = app.activeDocument.activeLayers[0];

  console.log("selectedLayer:", selectedLayer);  // Add this line to log the selected layer
  console.log("Is pixel layer:", selectedLayer.kind);  // Check if it's a pixel layer

  // Ensure a layer is selected and it's a pixel layer
  if (selectedLayer && selectedLayer.kind) {
    const cyanRedSliderColValue = String(parseFloat(cyanRedSliderCol.value));
    const magentaGreenSliderColValue = String(parseFloat(magentaGreenSliderCol.value));
    const yellowBlueSliderColValue = String(parseFloat(yellowBlueSliderCol.value));
    const preserveLumaColValue = preserveLumaCol.checked;    

    // Define the color adjustments using values from the UI elements
    const colorBalanceAdjustment = {
      _obj: "colorBalance",
      shadowLevels: [cyanRedSliderColValue, magentaGreenSliderColValue, yellowBlueSliderColValue],
      midtoneLevels: [cyanRedSliderColValue, magentaGreenSliderColValue, yellowBlueSliderColValue],
      highlightLevels: [cyanRedSliderColValue, magentaGreenSliderColValue, yellowBlueSliderColValue],
      preserveLuminosity: preserveLumaColValue,
    };

    try {
      // Log the colorBalanceAdjustment object before applying
      console.log("colorBalanceAdjustment:", colorBalanceAdjustment);

      // Apply the color adjustments using batchPlay
      const result = await photoshop.action.batchPlay(
        [
          {
            "_obj": "applyAdjustment",
            "to": {
              "_ref": selectedLayer,
              "_enum": "ordinal",
              "_value": "targetEnum"
            },
            "using": {
              "_obj": "colorBalance",
              "colorBalance": colorBalanceAdjustment,
              "globalAngle": 0,
            },
          },
        ],
        { synchronousExecution: true }
      );

      console.log("Adjustments applied:", result);
    } catch (error) {
      console.error("Error applying adjustments:", error);
      alert("An error occurred while applying color adjustments.");
    }
  } else {
    alert("Please select a pixel layer to apply color adjustments.");
  }
}

entrypoints.setup({
  commands: {
    // Define your commands here
  },
  panels: {
    // Define your panels here
  },
});

I’m not sure exactly what you’re trying to do, but if I understood correctly, I believe this example can help you.

const action = require("photoshop").action;
const core = require("photoshop").core;
const app = require("photoshop").app;
const alert = app.showAlert;
const doc = app.activeDocument;  
const {executeAsModal} = require("photoshop").core;
const {batchPlay} = require("photoshop").action;



document.querySelector("#aplicarajustes").addEventListener('click', async () => {
    await core.executeAsModal(async () => {
        await app.batchPlay([{"_obj":"make","_target":[{"_ref":"adjustmentLayer"}],"using":{"_obj":"adjustmentLayer","name":"ColorAjust","type":{"_obj":"colorBalance","highlightLevels":[0,0,0],"midtoneLevels":[0,0,0],"preserveLuminosity":true,"shadowLevels":[0,0,0]}}},
        {"_obj":"make","_target":[{"_ref":"layerSection"}],"from":{"_enum":"ordinal","_ref":"layer","_value":"targetEnum"},"layerSectionEnd":28,"layerSectionStart":27,"name":"ColorAjust","using":{"_obj":"layerSection","name":"ColorAjust"}}
        ])
    })
})


const ciano = document.querySelector("#ciano");
const magenta = document.querySelector("#magenta");
const amarelo = document.querySelector("#amarelo");

ciano.addEventListener("change", async () => {
    document.querySelector("#vciano").innerHTML = ciano.value;
    await ajusteColor(ciano.value, magenta.value, amarelo.value );
});

magenta.addEventListener("change", async () => {
    document.querySelector("#vmagenta").innerHTML = magenta.value;
    await ajusteColor(ciano.value, magenta.value ,amarelo.value);
});

amarelo.addEventListener("change", async () => {
    document.querySelector("#vamarelo").innerHTML = amarelo.value;
    await ajusteColor(ciano.value, magenta.value, amarelo.value);
});

async function ajusteColor(cianoValue, magentaValue, amareloAlue) {
    async function colorBalance() {
        await batchPlay(
            [
                {
                    _obj: "set",
                    _target: [
                        {
                            _ref: "adjustmentLayer",
                            _name: "ColorAjust", // Nome da sua camada de ajuste
                            _value: "targetEnum",
                        },
                    ],
                    to: {
                        _obj: "colorBalance",
                        midtoneLevels: [cianoValue, magentaValue, amareloAlue],
                    },
                    _options: { dialogOptions: "dontDisplay" },
                },
            ],
            { modalBehavior: "wait" }
        );
    }

    async function runModalFunction() {
        await executeAsModal(colorBalance);
    }

    await runModalFunction();
}

1 Like