Slider: Changes not happening in real time

Hello there,

i’m a noob just playing around. Sorry for bugging you and i hope i don’t waste too much of your time.
I made a small panel in UXP for learning how things work. I’m using Alchemist for listening to events. Thank you so much for this brilliant plugin. Without it, UXP would be totally unaccessible to me.
Today i made a slider that controls brightness on an adjustment layer and surprisingly i got this working. But when i’m dragging the slider i only see the histogram changing in real time.
The visible changes in the document happen on mouse up after dragging, although i’m using “input” instead of “change”. I searched for some working code examples but did not find anything useful.
I’m still pretty much confused about async and sync and i don’t really know what i’m doing (still learning). Maybe that is the problem here and someone can point me in the right direction.
This is the code i used:

document.getElementById("brightnessSlider").addEventListener("input", evt => {

   //console.log(`brightness value: ${evt.target.value}`);

  batchPlay(

      [

         {

            "_obj": "set",

            "_target": [

               {

                  "_ref": "adjustmentLayer",

                  "_enum": "ordinal",

                  "_value": "targetEnum"

               }

            ],

            "to": {

               "_obj": "brightnessEvent",

               "brightness": evt.target.value,

               "center": 0,

               "useLegacy": false

            },

            "_isCommand": false,

            "_options": {

               "dialogOptions": "dontDisplay"

            }

         }

      ],{

         "synchronousExecution": true,

         "modalBehavior": "execute",

         "historyStateInfo": {

            "name": "Brightness",

            "_target": {

               "_ref": 'document',

               "_enum": 'ordinal',

               "_value": 'targetEnum',

            }

         }

      });

        

})

It might have to do with the fact that Photoshop operations executed from scripts are generally quite slow.
I also made the experience that coupling slider input events directly with Photoshop operations performs quite bad.

Sliders are also quite sensitive to cursor movement, so even if you just try to click somewhere on the slider it will probably fire a few times. Even worse, when you drag it, the input event might fire several hundred times.

Now imagine that your dragging process takes 300ms and results in the input event firing 100 times.
If the Photoshop operation for applying the brightness takes 100ms, PS will be quickly overwhelmed, become unresponsive and maybe even crash.

1 Like

Thank you,
yes indeed i know that but: as far as i remember even ancient scriptUI was faster. And there was some options for that in the action descriptor code. Something like: stringIDtoTypeID blabla “Performance” “accelerated” what made sliders really fast in refreshing the document.
Maybe we could implement that in batchPlay somehow?

What bugs me is, that changes are only happening after dragging. Even when i hold the mouse totally still the UI is blocked (cursor just stops spinning). Is that normal behaviour?

Even In the old extendscript UI, I found that using .onChanging for sliders was very unstable. For just changing layer opacity then it would be OK. But anything that took longer to update, it would often cause lagging operation or freezing in some cases and it was different computer vs. computer. Therefore, I had to use onChange instead and only apply the changes when the user let go of the slider.

I haven’t even used sliders at all in UXP but I am guessing it may be similar just because it would be hard for it to keep up, depending on what the function is doing.

I usually just combine slider events with a debounce function. This way, it will wait for more events to come in within a given time frame and then execute the code (when no event came in for X milliseconds).

3 Likes

What i thought was that the communication between js and jsx in CEP was what us kept us from real time performance. In my opinion a plugin or extension should extend the possibilities of the base program (Photoshop). Now with UXP, what surely is still in development, i only see more restrictions so far while in CEP we had nearly endless possibilities. I would really love to see some things changing to positive. The sliders were always a thing that put me down.
In Lightroom for example Lua is used to communicate between Lightroom and the User interface. It’s a scripting language as well and real time changes are possible.
Hmmm…I mean as long as we are not calculating on a pixel layer and instead on adjustment layers it should be possible to translate simple gamma or brightness adjustments in real time or am i wrong?
We could build things missing in Photoshop that users really want. Like color wheels for example. Nothing to complex for batchPlay.

Nothing here is related to performance, although you could certainly get into a case where complex adjustments might not be able to update at a high frame rate.

You can validate this in a couple of ways:

  • Use your keyboard to adjust the slider (works with sp-slider only, AFAICT). I have a pretty high repeat rate, and changes to the adjustment layer are immediate and smooth.
  • Add a setTimeout delay to your batchPlays. (Make sure to capture event.target.value beforehand, so that the actions don’t end up using the latest value instead of the value at the time the timeout was created). If you set a delay of a second or more and move the slider slowly, being sure to release the mouse button before the timeout triggers, you’ll see the same animation occur on the Brightness slider in the Adjustments panel (and in the canvas).
  • Add mousemove event that gets the value from evt.clientX, a short setTimeout for the batchPlay, and DON’T hold the mouse button down while moving over the slider. Depending on how you’ve configured the batchPlay options, you can get real-time adjustment. Of course, this isn’t what you want from the user experience, but it does show that updates happen quite quickly.

Now, I’ll caveat this with: I’m not an expert on Ps internals or Batchplay. But I think what’s happening is that Ps is opting to suppress canvas and panel updates while the mouse button is pressed. I suspect there’s some code in Ps that modifies this behavior when you’re dragging the Brightness slider in the adjustment panel (so you can see the results in realtime), but in this case, Ps has no idea that’s what you’re after when you have a slider in your own panel, and so doesn’t modify how it updates the UI. I did check for a setting that you could use to tell Ps how to update the UI, but I didn’t see one.

I’ll note that this is not specific to sliders. Any time you hold down the mouse button can cause this behavior – Ps will just stop updating the UI until you release the mouse button.

So, it’s not that UXP/Ps can’t update the adjustment layer smoothly, but that Ps is suppressing UI updates while the mouse button is pressed. Odds are that this is for a good reason, since Ps doesn’t necessarily know what action is going to be triggered here, but perhaps the Ps team can add an option to batchPlay to let developers control if an action should always result in a UI update, rather than waiting for a mouse button release. [This is not something UXP controls—it’s up to the Ps team to decide.]

1 Like

That would be wonderful Kerri! Thank you for the detailed answer.

I suggest that you replace the input event with the mouseup event
document.getElementById("brightnessSlider").addEventListener("mouseup", evt => {})

Did anyone ever find a solution? I’m finding the exact same issue. I’ve tried different workarounds, but nothing seems to work… It’s a shame because it was possible (and quite easy) with CEP.

If you happen to have it handy, could you share the CEP code that makes it possible?

Using materialize to create the slider like so in my index.html:

<div class="range-field tooltipped" data-position="bottom" data-tooltip="<b>CLIC</b>: Adjust opacity of the color layer<br /><b>RIGHT CLIC</b>: Before/after <br /><b>DOUBLE CLIC</b>: Reset opacity to default.">
<input type="range" min="0" max="100" id="rgSelectiveColor">
</div>

Then in the main.js a simple addeventlistener on input was working for me:

// Adjust opacity when moving the slider
document.getElementById('rgSelectiveColor').addEventListener('input', function (event) {
            csInterface.evalScript('setLayerOpacity("'+ selectiveColorLayerName +'", "'+ cgGroupName +'" ,'+ rgSelectiveColor.value +')');
        });

The declaration for main.js in the index.html was made right before closing

And here goes setLayerOpacity() :

/******************************************************************************************/
/**
* SETS THE OPACITY FOR GIVEN LAYER
* @param {string} layerName - name of the layer to adjust
* @param {string} layerSetName - name of the layerset in which the layer is. use false if not in a layer set
* @param {int} layerOpacity - new opacity
*/
setLayerOpacity = function(layerName, layerSetName, layerOpacity){
	if(layerSetName == false)
		app.activeDocument.activeLayer = app.activeDocument.layers.getByName(layerName);
	else if(layerName == false && layerSetName != "")
		app.activeDocument.activeLayer = app.activeDocument.layerSets.getByName(layerSetName);
	else
		app.activeDocument.activeLayer = app.activeDocument.layerSets.getByName(layerSetName).artLayers.getByName(layerName);
	app.activeDocument.activeLayer.opacity = layerOpacity;	
};

This is one example amongst others. I had it working for adjustment layers as well (curves, brightness & contrast, levels, etc.).

1 Like

For anyone having issues with sliders not adjusting in PS features in real-time, this solved it for me and gives me somewhat good results no matter the size of the PSD file:

mouseDown event of the slider:

  • closing the layers panel (function below) seems to avoid having the adjustment being too jittery.
  • creation of a temporary document. Although it doesn’t seem to be necessary, it seems to give .better performances in the refresh rate

mouseUp event of the slider:

  • opening the layers panel
  • deletion of the temporary document

input event of the slider:

  • debouncing didn’t seem to help at all. Quite the opposite actually. So I removed it altogether. But depending on your code, it may prove to be helpful
  • after batchPlay is executed to change my adjustment layer according to my slider value, I select a different layer and come back to the adjustment layer. The change of layer seems to help with the refresh issue for some reason.
/**
 * Opens or closes the Layers panel.
 * @param {boolean} open - If true, opens the Layers panel; if false, closes it.
 * @returns {Promise<boolean>} True if the panel is visible, false otherwise.
 */
async function openCloseLayersPanel(open) {
    const panelIDToCheck = "panelid.static.layers"; // Replace with the ID you want to check
    const isVisible = await isPanelVisibleByID(panelIDToCheck);
    
    if (isVisible) {
        if (!open)
            menuCommand(1098);
        return false;
    } else {
        if (open)
            menuCommand(1098);
        return true;
    }
}

// creation of temp document
await photoshopCore.createTemporaryDocument({documentID:123});

// deletion of temp document
await photoshopCore.deleteTemporaryDocument({documentID:123});

If this alone does not work, I’ve found that closing the properties panel before applying the adjustment and opening it again right after helped. However, the unlike the layers panel which is opened/closed in the mouseup/down events, the properties panel trick seems to work only when used in the input event. Here’s the function I created for this:

/**
 * Opens or closes the Properties panel.
 * @param {boolean} open - If true, opens the Properties panel; if false, closes it.
 * @returns {Promise<boolean>} True if the panel is visible, false otherwise.
 */
async function openClosePropertiesPanel(open) {
    const panelIDToCheck = "panelid.static.properties"; // Replace with the ID you want to check
    const isVisible = await isPanelVisibleByID(panelIDToCheck);
    
    if (isVisible) {
        if (!open)
            menuCommand(6478);
        return false;
    } else {
        if (open)
            menuCommand(6478);
        return true;
    }
}

This last trick seems to help force refresh and take into consideration the new settings of the layer.

I hope this can help someone who runs in the same issue I did.

I think you could use this to enforce canvas redraw https://developer.adobe.com/photoshop/uxp/2022/ps_reference/media/photoshopcore/#redrawdocument

1 Like

I tried the redraw function, but it doesn’t fully work for me. The issue is when using a slider in my panel to update the slider of an adjustment layer. For some reason, while moving my panel’s slider PS interface is fully frozen – meaning the value of the adjustment layer doesn’t change at all and redraw() thus doesn’t do anything aside from redrawing the same image with no changes and slowing down the whole process.

Closing and opening the properties panel (or the layer panel, depending on the needs) does the trick, as it forces Photoshop to update the value(s) of the adjustment layer. It’s far from being perfect, but it works for now… I wish PS interface would freeze while we interact with panels.

I guess you are on a Mac. Most “hacks” work better on Windows than on Mac. Since the M1 either MacOS is more restrictive or the Adobe Team changed something in the code for the API which prevents live updating slider changes. It’s a PITA anyways even when it’s working. Especially when you are working with many layers. Directly editing on a pixel layer is way faster.