How to automate batch "Merge to HDR Efex Pro 2" from Nik Collection

Explorer ,
Feb 20, 2021 Feb 20, 2021

Copy link to clipboard

Copied

Hello, 

I sometimes make 5 the same photos with different exposure times. Than I use "Merge to HDR Efex Pro 2" from Nik Collection. I use it in this way:

In Photoshop I open the 5 JPG-files. And then:
- FILE

- AUTOMATE

-Merge to HDR Efex Pro 2
In the pop-up screen I than choose for "ADD OPEN FILES"
And after that I have to click a couple of buttons to go to the process.

But my question now is: Is there a way to make a batch process of this? As I sometimes have 50 sets of 5 photos I want to run through this proces. So after I did 1 set of 5 photo's I have to manually open a new set of 5 photo's and run that process.  So I'm looking for a way to have a batch process which would do the same for all the 50 sets. 

I know how to make an action, but the problem is that how I get that action to select 5 different photo's each time?

TOPICS
Actions and scripting, How to, Windows

Views

235

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Feb 20, 2021 Feb 20, 2021

Copy link to clipboard

Copied

A script can open multiple files in sets of say 5, and work through all 50 sets. Are the file names alphabetically sorting correctly? If not, there needs to be some sort of way to group them correctly using a text pattern if not numerical/alphabetical sorting...

 

Then the script can play an action to process the open files. The script can then save the combined file and then open up the next set of 5 images and repeat until all 250 files have been processed.

 

What file format and options are you saving the final combined image into? What is the filename based off?

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Feb 20, 2021 Feb 20, 2021

Copy link to clipboard

Copied

Hello Stephen,
The files are not always in a logical order, but it's easy for me to rename them to a logical order if that's needed to solve my "problem". For example I can rename them to "000.jpg" till "250.jpg".

The same for the file format, normally it's TIFF but I can make JPG 's of it as well, no problem.

I have tried a lot of programs that record my mouse-clicks and see if that works, but none of them did the job. 


Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Feb 20, 2021 Feb 20, 2021

Copy link to clipboard

Copied

Try the following script...

 

IMPORTANT: You must replace the placeholder action name of "Molten Lead" (line 82) to your recorded action name, likewise, you also need to change the action set name from "Default Actions" (line 84) to your recorded action set name. Ensure that you keep the double straight quotes around the names.

 

JPEG, TIFF and PSD files are expected, you can modify the input filter to add more file formats.

 

I have made assumptions regarding the TIFF save options and file output location, filename and suffix. Files are saved to a sub-directory of the input folder named "Batch HDR Output". All of these assumptions can be easily modified, just ask if something does not suit.

 

Input files are required to be alphabetically sorted in order to be correctly processed in the expected sets of 5 images. I.E. A prefix of 0001 to 0005, 0006 to 0010 etc. The quantity of source files must be evenly divisible by 5. There is a manual check for this where you have the option to cancel or continue.

 

Instructions linked at the foot of this post for saving and running scripts.

 

 

/* 

Batch Merge to Nik HDR Efex Pro 2 v1.jsx
by Stephen Marsh - 2021

How to automate batch 'Merge to HDR Efex Pro 2' from Nik Collection
https://community.adobe.com/t5/photoshop/how-to-automate-batch-quot-merge-to-hdr-efex-pro-2-quot-from-nik-collection/td-p/11845482
https://www.photoshopgurus.com/forum/threads/loading-all-images-in-a-folder-into-groups-of-3-is-it-possible.64634/ - Paul MR
https://forums.adobe.com/thread/2144091
https://forums.adobe.com/message/11236820#11236820

NOTE:
* No Files should be open.
* There is no error checking, the input folder should contain even quantities of 5 alphabetically sorting images.
* DEPENDENCY: An action set/action with the appropriate Nik plug-in settings is required to merge the open files into a single file

*/

#target photoshop

/* Start Open Document Error Check - Part A: If */

if (app.documents.length === 0) {

    // Save and disable dialogs
    var restoreDialogMode = app.displayDialogs;
    app.displayDialogs = DialogModes.NO;

    batchSetProcessing();

    function batchSetProcessing() {

        // Select the input folder
        inputFolder = Folder.selectDialog('Please select the folder with files to process');
        if (inputFolder === null) return;

        // Create the output sub-directory
        var outputFolder = Folder(decodeURI(inputFolder + '/Batch HDR Output'));
        if (!outputFolder.exists) outputFolder.create();

        // Limit the file format input
        var fileList = inputFolder.getFiles(/\.(jpg|jpeg|tif|tiff|psd)$/i);

        // Force alpha-numeric list sort
        // Use .reverse() for the 1st filename in the merged file
        // Remove .reverse() for the 5th filename in the merged file
        fileList.sort().reverse();

        // Manually validate the input count vs. output count
        var inputCount = fileList.length;
        var cancelScript = confirm(inputCount + ' input files stacked into sets of 5 will produce ' + inputCount / 5 + ' file sets. Press "Yes" to continue or "No" to cancel.');
        // Test if no returns false, then terminate the script
        if (cancelScript === false) {
            alert('Script cancelled!');
            return;
        }

        // Loop through and open the file sets
        while (fileList.length) {
            // Sets of 5 files
            for (var a = 0; a < 5; a++) {
                try {
                    app.open(fileList.pop());
                } catch (e) { }
            }

            processOpenImages();

        }

        // End of script notification
        var outputList = outputFolder.getFiles(/\.(tif|tiff)$/i);
        alert('Script completed!' + '\n' + outputList.length + ' HDR files saved to:' + '\n' + outputFolder.fsName);

        function processOpenImages() {

            try {

                /* Start doing stuff */

                // Play the Nik Merge to HDR Efex Pro 2 action

                // Action to run
                var actionName = 'Molten Lead';
                // Action set to run
                var actionSet = 'Default Actions';
                // Run the nominated action and set
                app.doAction(actionName, actionSet);

                /* Finish doing stuff */

            } catch (e) { }

            // Save name+suffix & save path
            var Name = app.activeDocument.name.replace(/\.[^\.]+$/, '');
            var saveFile = File(outputFolder + '/' + Name + '_Batch-HDR' + '.tif');

            // Call the save function
            saveTIFF(saveFile);

            // Close all open files without saving
            while (app.documents.length) {
                app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
            }

            // Function to save TIFF
            function saveTIFF(saveFile) {
                tiffSaveOptions = new TiffSaveOptions();
                tiffSaveOptions.embedColorProfile = true;
                tiffSaveOptions.byteOrder = ByteOrder.IBM;
                tiffSaveOptions.transparency = true;
                // Change .layers to true to preserve layers
                tiffSaveOptions.layers = false;
                tiffSaveOptions.layerCompression = LayerCompression.ZIP;
                tiffSaveOptions.interleaveChannels = true;
                tiffSaveOptions.alphaChannels = true;
                tiffSaveOptions.annotations = true;
                tiffSaveOptions.spotColors = true;
                tiffSaveOptions.saveImagePyramid = false;
                // Image compression = NONE | JPEG | TIFFLZW | TIFFZIP
                tiffSaveOptions.imageCompression = TIFFEncoding.TIFFLZW;
                // Save as
                app.activeDocument.saveAs(saveFile, tiffSaveOptions, true, Extension.LOWERCASE);
            }
        }
    }

    // Restore saved dialogs
    app.displayDialogs = restoreDialogMode;

}

/* Finish Open Document Error Check - Part A: If */


/* Start Open Document Error Check - Part B: Else */

else {

    alert('Batch Merge to Nik HDR Efex Pro 2 v1:' + '\n' + 'Please close all open documents before running this script!');
}

/* Finish Open Document Error Check - Part B: Else */

 

 

https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html

 

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Feb 21, 2021 Feb 21, 2021

Copy link to clipboard

Copied

Wow Stephen, that's impressive, respect. I will try the script and let you know. Thanks a million in advance.

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Feb 22, 2021 Feb 22, 2021

Copy link to clipboard

Copied

Hello Stephen,

I've tried your script but unfortunately it doesn't do the trick. The problem is, that when the "Merge to HDR Efex Pro 2" pop-up screen shows I still need to manually press the buttons in this plugin to get the plugin to work. The script opens up the 5 files and makes a new directory. It also opens up the NIK-filter "Merge to HDR Efex Pro 2", but than stops. 

When the filter opens I need to push the button which says: "Add opened files" and after that I also have to manually push some buttons in that filter.

 

Is there a possibility to use a script that also runs a non-Photoshop-program? 


Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Feb 22, 2021 Feb 22, 2021

Copy link to clipboard

Copied

When you wrote:

 

Stephen_A_Marsh_0-1613992736619.png

In Photoshop I open the 5 JPG-files. And then:
- FILE

- AUTOMATE

-Merge to HDR Efex Pro 2
In the pop-up screen I than choose for "ADD OPEN FILES"
And after that I have to click a couple of buttons to go to the process.

...

I know how to make an action, but the problem is that how I get that action to select 5 different photo's each time?


By @Appelmoes

 

I naturally thought that you meant that the action recorded the clicks that you made in the GUI of the software.

 

The entire script proposal centred around your action not requiring human intervention. 

 

What does an action record?

 

I'm also curious what the ScriptingListener plug-in records after opening the files and running through the Nik plug-in.

 

https://helpx.adobe.com/au/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListenerplugi...

 

Perhaps that is where your macro recording software could come in and playback the button pressing in the Nik plug-in.

 

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Feb 22, 2021 Feb 22, 2021

Copy link to clipboard

Copied

Sorry if I wasn't clear enough.


Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Feb 22, 2021 Feb 22, 2021

Copy link to clipboard

Copied

No worries, the script was a variant of others that I have hacked before, it gave me the chance to practice and refine things a tad.

 

At least your process can be semi-automated, you are in a better position and no worse off than before.

 

Have you looked at the Adobe Merge to HDR? There is a script to automate that if I recall correctly.

 

I'd be interested in seeing the 5 imaages and the final Nik HDR.

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Mar 03, 2021 Mar 03, 2021

Copy link to clipboard

Copied

Hello Stephen, sorry for not responding any earlier.

Unfortunately the Adobe Merge to HDR function doesn't come near the output the Nik HDR Efex Pro function.

The upper is the result of merging 3 photos in NIK, the bottom one is the result of Adobe.

hdr.jpg

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 04, 2021 Mar 04, 2021

Copy link to clipboard

Copied

Thank you for the example.

 

There are many options in the Photoshop Merge to HDR Pro script, one can get closer to the Nik result with the Photorealistic + equalize option:

 

photorealistic-equalize.jpg

 

The problem remains though, this is not for batch processing.

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 04, 2021 Mar 04, 2021

Copy link to clipboard

Copied

And here is the Batch script that I previously mentioned:

 

https://github.com/davidmilligan/PhotoshopBatchHDR

 

https://raw.githubusercontent.com/davidmilligan/PhotoshopBatchHDR/master/Batch%20HDR.jsx

 

hdr-gui.png

 

#target photoshop

/*********************************************************************
 Batch HDR Script by David Milligan
*********************************************************************/

/*********************************************************************
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
**********************************************************************/

/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING

<javascriptresource>
<name>Batch HDR...</name>
<menu>automate</menu>
</javascriptresource>

// END__HARVEST_EXCEPTION_ZSTRING
*/

//these lines import the 'Merge To HDR.jsx' script that is built in to photoshop, we will make calls to that script and some of the scripts that it includes
var runMergeToHDRFromScript = true;
var g_ScriptFolderPath = app.path + "/"+ localize("$$$/ScriptingSupport/InstalledScripts=Presets/Scripts");
var g_ScriptPath = File( g_ScriptFolderPath+'/Merge To HDR.jsx' );
$.evalFile( g_ScriptPath ); 
//$.level = 2;

//default settings:
mergeToHDR.useAlignment = false;
mergeToHDR.useACRToning = false;
var numberOfBrackets = 3;
var userCanceled = false;
var sourceFolder;
var outputFolder;
var saveType = "JPEG";
var jpegQuality = 10;
var progress;
var statusText;
var progressWindow;
var fileMask = "*";
var outputFilename = "hdr_output_";
var zeroPadding = 5;

var hdrRadius = 100;
var hdrStrength = 0.5;
var hdrGamma = 1.0;
var hdrExposure = 0.0;
var hdrDetail = 100;
var hdrShadow = 0;
var hdrHighlights = 0;
var hdrVibrance = 20;
var hdrSaturation = 30;
var hdrSmooth = false;
var hdrDeghosting = kMergeToHDRDeghostBest;//kMergeToHDRDeghostOff
var hdrCurve = "0,0,255,255";
var estTimeRemaining = "";

var previewDoc;
var originalDoc;

function main()
{
    promptUser();
    
    //make sure user didn't cancel
    if(sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists && numberOfBrackets > 0)
    {
        initializeProgress();
        var files =  sourceFolder.getFiles(fileMask);
        files.sort();
        var currentFileList = new Array();

        var numberOfFiles = files.length;

        /* convert numberOfFiles to a string to make sure zeropaddingis high enough to cover all files */

        var numberOfFilesStr = "" + (numberOfFiles / numberOfBrackets);
        if (zeroPadding > 0 && zeroPadding < numberOfFilesStr.length)
        {
            zeroPadding = numberOfFilesStr.length;
        }

        for(var index = 0;  index < numberOfFiles; index++)
        {
            if((index % numberOfBrackets) == numberOfBrackets - 1)
            {
                var start = new Date();
                progress.value = 100 * index / numberOfFiles;
                currentFileList.push(files[index]);
                if(userCanceled) break;
                if(numberOfBrackets > 1)
                {
                    statusText.text = "Merging files "+(index-numberOfBrackets+2)+" - "+(index+1)+" of "+numberOfFiles + estTimeRemaining;
                    //for braketed exposures use the mergeToHDR script to merge the files into a single 32 bit image
                    mergeToHDR.outputBitDepth= 32;
                    
                    mergeToHDR.mergeFilesToHDR( currentFileList, mergeToHDR.useAlignment, hdrDeghosting );
                    statusText.text = "Toning files "+(index-numberOfBrackets+2)+" - "+(index+1)+" of "+numberOfFiles+ estTimeRemaining;
                }
                else
                {
                    statusText.text = "Loading file "+(index+1)+" of "+numberOfFiles+ estTimeRemaining;
                    //otherwise just open the file
                    doOpenFile(files[index]);
                    statusText.text = "Toning file "+(index+1)+" of "+numberOfFiles+ estTimeRemaining;
                }
                progress.value = 100 * (index + numberOfBrackets / 2 ) / numberOfFiles;
                if(userCanceled) break;
                try
                {
                    if(app.activeDocument != null && outputBitDepth < 32)
                    {
                        //apply the actual tone mapping to the HDR image to get it back down to 8 bits
                        doHDRToning();
                    }
                }
                catch(error)
                {
                    alert(error + "\nCheck number of files in source folder");
                    break;
                }
                
                //save the result and close
                //TODO: add leading zeros to index in filename
                
                if(numberOfBrackets > 1)
                {
                    statusText.text = "Saving result "+(index-numberOfBrackets+2)+" - "+(index+1)+" of "+numberOfFiles+ estTimeRemaining;
                }
                else
                {
                    statusText.text = "Saving result "+(index+1)+" of "+numberOfFiles+ estTimeRemaining;
                }
                if(userCanceled) break;
                doSaveFile(outputFolder.absoluteURI + "/" + outputFilename + ZeroPad(Math.round((index + 1)/numberOfBrackets), zeroPadding) );
                activeDocument.close(SaveOptions.DONOTSAVECHANGES);
                
                //reset our file list
                currentFileList = new Array();
                
                //calculate time remaining
                var end = new Date();
                var timeElapsed = end.getTime() - start.getTime();
                var mins = timeElapsed / 60000 * ((numberOfFiles - index - 1) / numberOfBrackets);
                estTimeRemaining = " | Remaining: " + ZeroPad((mins / 60).toFixed(0),2) + ":" + ZeroPad((mins % 60).toFixed(0),2);
            }
            else
            {
                currentFileList.push(files[index]);
            }
        }
        progressWindow.hide();
    }
}

function doOpenFile(filename)
{
    
    const eventOpen = app.charIDToTypeID('Opn ');
    var desc = new ActionDescriptor();
    desc.putPath( typeNULL, new File( filename ) );
    desc.putBoolean( kpreferXMPFromACRStr, true ); //not sure what this does or if it is needed
    executeAction( eventOpen, desc, DialogModes.NO );
    //if we don't convert the image to 32bit the mergeToHDR script will not tone our image when we call it, it will simply downconvert it to 8 bit
    convertTo32Bit ();
}

function convertTo32Bit()
{
    var idCnvM = charIDToTypeID( "CnvM" );
    var desc6 = new ActionDescriptor();
    var idDpth = charIDToTypeID( "Dpth" );
    desc6.putInteger( idDpth, 32 );
    var idMrge = charIDToTypeID( "Mrge" );
    desc6.putBoolean( idMrge, false );
    var idRstr = charIDToTypeID( "Rstr" );
    desc6.putBoolean( idRstr, false );
    executeAction( idCnvM, desc6, DialogModes.NO );
}

function doSaveFile(filename)
{
    if(saveType == "JPEG")
    {
        var jpgSaveOptions = new JPEGSaveOptions();
        jpgSaveOptions.embedColorProfile = true;
        jpgSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
        jpgSaveOptions.matte = MatteType.NONE;
        jpgSaveOptions.quality = jpegQuality;
        activeDocument.saveAs(new File(filename), jpgSaveOptions, true /*Save As Copy*/, Extension.LOWERCASE /*Append Extention*/);
    }
    else if(saveType == "TIFF")
    {
        var tifSaveOptions = new TiffSaveOptions();
        tifSaveOptions.embedColorProfile = true;
        activeDocument.saveAs(new File(filename), tifSaveOptions, true /*Save As Copy*/, Extension.LOWERCASE /*Append Extention*/);
    }
    else if(saveType == "TIFF LZW")
    {
        var tifSaveOptions = new TiffSaveOptions();
        tifSaveOptions.embedColorProfile = true;
        tifSaveOptions.imageCompression = TIFFEncoding.TIFFLZW;
        activeDocument.saveAs(new File(filename), tifSaveOptions, true /*Save As Copy*/, Extension.LOWERCASE /*Append Extention*/);
    }
    else if(saveType == "TIFF ZIP")
    {
        var tifSaveOptions = new TiffSaveOptions();
        tifSaveOptions.embedColorProfile = true;
        tifSaveOptions.imageCompression = TIFFEncoding.TIFFZIP;
        activeDocument.saveAs(new File(filename), tifSaveOptions, true /*Save As Copy*/, Extension.LOWERCASE /*Append Extention*/);
    }
    else if(saveType == "Radiance")
   	{
   		var idsave = charIDToTypeID( "save" );
		var desc3 = new ActionDescriptor();
		var idAs = charIDToTypeID( "As  " );
		desc3.putString( idAs, """Radiance""" );
		var idIn = charIDToTypeID( "In  " );
		desc3.putPath( idIn, new File(filename + ".hdr") );
		var idDocI = charIDToTypeID( "DocI" );
		desc3.putInteger( idDocI, 58 );
		var idLwCs = charIDToTypeID( "LwCs" );
		desc3.putBoolean( idLwCs, true );
		var idsaveStage = stringIDToTypeID( "saveStage" );
		var idsaveStageType = stringIDToTypeID( "saveStageType" );
		var idsaveSucceeded = stringIDToTypeID( "saveSucceeded" );
		desc3.putEnumerated( idsaveStage, idsaveStageType, idsaveSucceeded );
		executeAction( idsave, desc3, DialogModes.NO );
	}
    else if(saveType == "OpenEXR")
   	{
   		var idsave = charIDToTypeID( "save" );
		var desc5 = new ActionDescriptor();
		var idAs = charIDToTypeID( "As  " );
		desc5.putString( idAs, """OpenEXR""" );
		var idIn = charIDToTypeID( "In  " );
		desc5.putPath( idIn, new File(filename + ".exr") );
		var idDocI = charIDToTypeID( "DocI" );
		desc5.putInteger( idDocI, 58 );
		var idLwCs = charIDToTypeID( "LwCs" );
		desc5.putBoolean( idLwCs, true );
		var idsaveStage = stringIDToTypeID( "saveStage" );
		var idsaveStageType = stringIDToTypeID( "saveStageType" );
		var idsaveSucceeded = stringIDToTypeID( "saveSucceeded" );
		desc5.putEnumerated( idsaveStage, idsaveStageType, idsaveSucceeded );
		executeAction( idsave, desc5, DialogModes.NO );
	}
    else
    {
        activeDocument.saveAs(new File(filename), undefined, true /*Save As Copy*/, Extension.LOWERCASE /*Append Extention*/);
    }
}

function doHDRToning()
{
    //create the ActionDescriptor that describes the HDR toning settings to use
    var hdDesc = new ActionDescriptor;
    hdDesc.putInteger( stringIDToTypeID( 'version' ), 6 );//I'm not sure what this does
    hdDesc.putInteger(  kmethodStr, 3 );// the toning method to use, 3 = local adaptation
    hdDesc.putDouble( stringIDToTypeID( 'radius' ), hdrRadius );
    hdDesc.putDouble( stringIDToTypeID( 'threshold' ), hdrStrength );// strength
    hdDesc.putDouble( stringIDToTypeID( 'center' ), hdrGamma );// gamma
    hdDesc.putDouble( stringIDToTypeID( 'brightness' ), hdrExposure );// exposure
    hdDesc.putDouble( stringIDToTypeID( 'detail' ), hdrDetail );
    hdDesc.putDouble( stringIDToTypeID( 'shallow' ), hdrShadow );
    hdDesc.putDouble( stringIDToTypeID( 'highlights' ), hdrHighlights );
    hdDesc.putDouble( stringIDToTypeID( 'vibrance' ), hdrVibrance );
    hdDesc.putDouble( stringIDToTypeID( 'saturation' ), hdrSaturation);
    hdDesc.putBoolean( stringIDToTypeID( 'smooth' ), hdrSmooth );
    hdDesc.putBoolean( stringIDToTypeID( 'deghosting' ), hdrDeghosting );
    
    //create the tone curve
    var cDesc = new ActionDescriptor;
    cDesc.putString( stringIDToTypeID( 'name' ), 'Default');
    var cList = new ActionList;
    var points = hdrCurve.split(',');
    for(var i = 0; i < points.length; i++)
    {
        if(i % 2 == 1)
        {
            var pDesc = new ActionDescriptor;
            pDesc.putDouble( stringIDToTypeID( 'horizontal' ), points[i-1] );
            pDesc.putDouble( stringIDToTypeID( 'vertical' ), points[i] );
            pDesc.putBoolean( keyContinuity , false );// ?????
            cList.putObject( charIDToTypeID( 'Pnt ' ), pDesc );
        }
    }
    cDesc.putList( stringIDToTypeID( 'curve' ), cList );
    hdDesc.putObject( kclassContour, classShapingCurve, cDesc );
    
    //call the script that actually invokes the toning plugin
    convertFromHDRNoDialog( outputBitDepth, hdDesc );
}

function initializeProgress()
{
    progressWindow = new Window("palette { text:'Batch HDR Progress', \
        statusText: StaticText { text: 'Processing Images...', preferredSize: [350,20] }, \
        progressGroup: Group { \
            progress: Progressbar { minvalue: 0, maxvalue: 100, value: 0, preferredSize: [300,20] }, \
            cancelButton: Button { text: 'Cancel' } \
        } \
    }");
    statusText = progressWindow.statusText;
    progress = progressWindow.progressGroup.progress;
    progressWindow.progressGroup.cancelButton.onClick = function() { userCanceled = true; }
    progressWindow.show();
}

function promptUser()
{
    var setupWindow = new Window("dialog { orientation: 'row', text: 'Batch HDR', alignChildren:'top', \
        leftGroup: Group { orientation: 'column', alignChildren:'fill', \
            inputPanel: Panel { text: 'Input', \
                sourceGroup: Group { \
                    sourceBox: EditText { characters: 40, text: '' }, \
                    sourceBrowse: Button { text: 'Browse' } \
                }, \
                bracketGroup: Group{ \
                    bracketLabel: StaticText { text: 'Number of Brackets: ' }, \
                    bracketBox: EditText { characters: 2 }, \
                    filterLabel: StaticText { text: 'File Filter: ' }, \
                    filterText: EditText { characters: 5 }, \
                    alignCheckBox: Checkbox { text: 'Align' }\
                    deghostLabel: StaticText { text: 'Deghost: ' }\
                    deghostDropDown: DropDownList { }, \
                } \
            }, \
            toningPanel: Panel { text: 'Toning', orientation:'row', alignChildren:'top' } ,\
            outputPanel: Panel { text: 'Output', \
                outputGroup: Group { \
                    outputBox: EditText { characters: 40, text: '' }, \
                    outputBrowse: Button { text: 'Browse' } \
                }, \
                outputOptionsGroup: Group { \
                    outputFilenameLabel: StaticText { text: 'Filename Format: ' }, \
                    outputFilenameText: EditText { characters: 10 }, \
                    outputFilenamePost: StaticText { text: '00001.jpg' }, \
                }, \
                saveSettingsGroup: Group { \
                    saveTypeLabel: StaticText { text: 'Save As: ' }, \
                    saveDropDown: DropDownList { }, \
                    jpegQualityLabel: StaticText { text: 'JPEG Quality (1-10): ' }, \
                    jpegQualityText: EditText { characters: 2}, \
                    outputBitDepthLabel: StaticText { text: 'Bit Depth', enabled:false }, \
                    outputBitDepthDropDown: DropDownList { enabled:false }, \
                } \
            } \
        }, \
        rightGroup: Group { orientation: 'column', alignChildren:'fill', \
            okButton: Button { text: 'OK', enabled: false } \
            cancelButton: Button { text: 'Cancel' } \
        } \
    } ");
    
    generateToningPanel(setupWindow.leftGroup.toningPanel);
    
    //shortcut variables
    var sourceBox = setupWindow.leftGroup.inputPanel.sourceGroup.sourceBox;
    var sourceBrowse = setupWindow.leftGroup.inputPanel.sourceGroup.sourceBrowse;
    var bracketBox = setupWindow.leftGroup.inputPanel.bracketGroup.bracketBox;
    var filterText = setupWindow.leftGroup.inputPanel.bracketGroup.filterText;
    var alignCheckBox = setupWindow.leftGroup.inputPanel.bracketGroup.alignCheckBox;
    var outputBox = setupWindow.leftGroup.outputPanel.outputGroup.outputBox;
    var outputBrowse = setupWindow.leftGroup.outputPanel.outputGroup.outputBrowse;
    var outputFilenameText = setupWindow.leftGroup.outputPanel.outputOptionsGroup.outputFilenameText;
    var outputFilenamePost = setupWindow.leftGroup.outputPanel.outputOptionsGroup.outputFilenamePost;
    var saveDropDown = setupWindow.leftGroup.outputPanel.saveSettingsGroup.saveDropDown;
    var jpegQualityText = setupWindow.leftGroup.outputPanel.saveSettingsGroup.jpegQualityText;
    var jpegQualityLabel = setupWindow.leftGroup.outputPanel.saveSettingsGroup.jpegQualityLabel;
    var outputBitDepthDropDown = setupWindow.leftGroup.outputPanel.saveSettingsGroup.outputBitDepthDropDown;
    var outputBitDepthLabel = setupWindow.leftGroup.outputPanel.saveSettingsGroup.outputBitDepthLabel;
    var okButton = setupWindow.rightGroup.okButton;
    var cancelButton = setupWindow.rightGroup.cancelButton;
    var toningPanel = setupWindow.leftGroup.toningPanel;
    var deghostDropDown = setupWindow.leftGroup.inputPanel.bracketGroup.deghostDropDown;
    
    //set default values
    bracketBox.text = numberOfBrackets;
    filterText.text = fileMask;
    //mergeToHDR.useAlignment = true;
    alignCheckBox.value = mergeToHDR.useAlignment;
    outputFilenameText.text = outputFilename;
    jpegQualityText.text = jpegQuality;
    saveDropDown.add("item", "JPEG");
    saveDropDown.add("item", "TIFF");
    saveDropDown.add("item", "TIFF LZW");
    saveDropDown.add("item", "TIFF ZIP");
    saveDropDown.add("item", "Radiance");
    saveDropDown.add("item", "OpenEXR");
    saveDropDown.add("item", "PSD");
    saveDropDown.selection = 0;
    outputBitDepthDropDown.add("item", "8");
    outputBitDepthDropDown.add("item", "16");
    outputBitDepthDropDown.add("item", "32");
    outputBitDepthDropDown.selection = 0;
    
    var generateDeghostDropDownList = function(count)
    {
		deghostDropDown.removeAll()
		deghostDropDown.add("item", "Best");
		deghostDropDown.add("item", "Off");
		for(var i = 0; i < count; i++)
		{
			deghostDropDown.add("item", i);
		}
		deghostDropDown.selection = 0;
    }
    generateDeghostDropDownList(numberOfBrackets);
    
    //event handlers
    sourceBox.onChange = function()
    {
        sourceFolder = new Folder(sourceBox.text);
        okButton.enabled = sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists;
    };
    sourceBrowse.onClick = function()
    {
        sourceFolder = Folder.selectDialog ("Select the source folder");
        if(sourceFolder != null)
        {
            sourceBox.text = sourceFolder.fullName;

            if (outputFolder == null)
            {
                outputFolder = sourceFolder;
                outputBox.text = outputFolder.fullName;
            }
        }
        okButton.enabled = sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists;
    };
    bracketBox.onChange = function()
    { 
		numberOfBrackets = bracketBox.text;
		generateDeghostDropDownList(numberOfBrackets);
    };
    filterText.onChange = function() { fileMask = filterText.text; };
    alignCheckBox.onClick = function() { mergeToHDR.useAlignment = alignCheckBox.value; };
    deghostDropDown.onChange = function() 
    { 
    	if(this.selection.text == "Best") 
    		hdrDeghosting = kMergeToHDRDeghostBest;
    	else if(this.selection.text == "Off")
    		hdrDeghosting = kMergeToHDRDeghostOff;
    	else
    		hdrDeghosting = Number(this.selection.text);
    		
    };
    outputBox.onChange = function()
    {
        outputFolder = new Folder(outputBox.text);
        okButton.enabled = sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists;
    };
    outputBrowse.onClick = function()
    {
        outputFolder = Folder.selectDialog ("Select the output folder");
        if(outputFolder != null)
        {
            outputBox.text = outputFolder.fullName;
        }
        okButton.enabled = sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists;
    };
    outputFilenameText.onChange = function() { outputFilename = outputFilenameText.text; };

    saveDropDown.onChange = function()
    {
        saveType = saveDropDown.selection.text;
        jpegQualityText.enabled = saveDropDown.selection.text == "JPEG";
        jpegQualityLabel.enabled = saveDropDown.selection.text == "JPEG";
        if(saveDropDown.selection.text == "JPEG")
        {
            outputBitDepthDropDown.selection = 0;
        }
        else if(saveDropDown.selection.text == "OpenEXR" || saveDropDown.selection.text == "Radiance")
        {
            outputBitDepthDropDown.selection = 2;
        }
        outputBitDepthDropDown.enabled = saveDropDown.selection.text != "JPEG" && saveDropDown.selection.text != "Radiance" && saveDropDown.selection.text != "OpenEXR";
        outputBitDepthLabel.enabled = outputBitDepthDropDown.enabled;
        
    };
    jpegQualityText.onChange = function() { jpegQuality = jpegQualityText.text; };
    outputBitDepthDropDown.onChange = function()
    { 
        outputBitDepth = outputBitDepthDropDown.selection.text; 
        toningPanel.enabled = outputBitDepth != 32;
    }
    okButton.onClick = function() { setupWindow.hide(); cleanUpPreviews(); };
    cancelButton.onClick = function() { sourceFolder = null, setupWindow.hide(); cleanUpPreviews(); };
    
    saveDropDown.onChange();
    outputBitDepthDropDown.onChange();
    
    setupWindow.show();
}

function cleanUpPreviews()
{
    if(originalDoc != null)
    {
        originalDoc.close(SaveOptions.DONOTSAVECHANGES);
        originalDoc = null;
    }
    if(previewDoc != null)
    {
        previewDoc.close(SaveOptions.DONOTSAVECHANGES);
        previewDoc = null;
    }
}

function generateToningPanel(toningPanel)
{
    var leftToningGroup = toningPanel.add("group{orientation:'column',alignChildren:'fill'}");
    var rightToningGroup = toningPanel.add("group{orientation:'column',alignChildren:'fill'}");
    var presetGroup = leftToningGroup.add("group{orientation:'row'}");
    var presetDropDown = presetGroup.add("dropdownlist");
    var loadPresetButton = presetGroup.add("button", undefined, "Load Preset");
    var edgePanel = leftToningGroup.add("panel",undefined,"Edge Glow");
    var radiusSlider = createSliderControl(edgePanel.add("group"), "  Radius: ", "px", 0, 500, 0, hdrRadius, function(newValue){ hdrRadius = newValue; });
    var strengthSlider = createSliderControl(edgePanel.add("group"), "Strength: ", "", 0, 4.0, 2, hdrStrength, function(newValue){ hdrStrength = newValue; });
    var edgeGroup = edgePanel.add("group");
    var smoothEdgesBox = edgeGroup.add("checkbox",undefined, "Smooth Edges");
    var detailPanel = leftToningGroup.add("panel",undefined,"Tone and Detail");
    var gammaSlider = createSliderControl(detailPanel.add("group"), "  Gamma: ", "", 0.1, 2.0, 2, hdrGamma, function(newValue){ hdrGamma = newValue; });
    var exposureSlider = createSliderControl(detailPanel.add("group"), "Exposure: ", "", -5.0, 5.0, 2, hdrExposure, function(newValue){ hdrExposure = newValue; });
    var detailSlider = createSliderControl(detailPanel.add("group"), "     Detail: ", "%", -300, 300, 0, hdrDetail, function(newValue){ hdrDetail = newValue; });
    var advancedPanel = leftToningGroup.add("panel",undefined,"Advanced");
    var shadowSlider = createSliderControl(advancedPanel.add("group"), "  Shadow: ", "%", -100, 100, 0, hdrShadow, function(newValue){ hdrShadow = newValue; });
    var highlightSlider = createSliderControl(advancedPanel.add("group"), " Highlight: ", "%",  -100, 100, 0, hdrHighlights, function(newValue){ hdrHighlights = newValue; });
    var vibranceSlider = createSliderControl(advancedPanel.add("group"), "  Vibrance: ", "%",  -100, 100, 0, hdrVibrance, function(newValue){ hdrVibrance = newValue; });
    var saturationSlider = createSliderControl(advancedPanel.add("group"), "Saturation: ", "%",  -100, 100, 0, hdrSaturation, function(newValue){ hdrSaturation = newValue; });
    var toningCurvePanel = leftToningGroup.add("panel{text:'Toning Curve',alignChildren:'fill'}");
    var curveBox = toningCurvePanel.add("edittext", undefined, hdrCurve);
    //right side (preview panel)
    var previewGroup = rightToningGroup.add("panel", undefined, "Preview");
    var selectPreviewButton = previewGroup.add("button",undefined,"Select File(s)...");
    var previewButton = previewGroup.add("button",undefined,"Update Preview");
    var zoomGroup = previewGroup.add("group");
    zoomGroup.add("statictext",undefined, "zoom");
    var zoomBox = zoomGroup.add("edittext { text: '100', characters: 3, enabled: false } ");
    var previewZoomSlider = previewGroup.add("slider { minvalue: 10, maxvalue: 200, value: 100, enabled: false }");
    
    //default values
    smoothEdgesBox.value = hdrSmooth;
    previewButton.enabled = app.documents.length > 0;
    var presetFiles = getPresetFiles();
    var updateSliders = function()
    {
        radiusSlider(hdrRadius);
        strengthSlider(hdrStrength);
        smoothEdgesBox.value = hdrSmooth;
        exposureSlider(hdrExposure);
        gammaSlider(hdrGamma);
        detailSlider(hdrDetail);
        shadowSlider(hdrShadow);
        highlightSlider(hdrHighlights);
        vibranceSlider(hdrVibrance);
        saturationSlider(hdrSaturation);
        curveBox.text = hdrCurve;
    }
    if(presetFiles.length > 0)
    {
        for(var f in presetFiles)
        {
            presetDropDown.add("item", presetFiles[f].displayName.replace(".hdt",""));
        }
        presetDropDown.selection = 0;
        loadPreset(presetFiles[0]);
        presetDropDown.onChange = function()
        {
            loadPreset(presetFiles[presetDropDown.selection.index]);
            updateSliders();
        };
    }
    
    //event handlers
    loadPresetButton.onClick = function()
    {
        loadPreset(null);
        updateSliders();
    };
    smoothEdgesBox.onClick = function () { hdrSmooth = smoothEdgesBox.value; };
    curveBox.onChange = function () { hdrCurve = curveBox.text; };
    selectPreviewButton.onClick = function()
    {
        var selectedFiles = File.openDialog("Select file(s) to load for preview", "*.*", true);
        if(selectedFiles != null)
        {
            cleanUpPreviews();
            if(selectedFiles instanceof Array)
            {
                if(selectedFiles.length > 1)
                {
                    mergeToHDR.outputBitDepth= 32;
                    mergeToHDR.mergeFilesToHDR( selectedFiles, false, -2 );
                }
                else
                {
                    doOpenFile(selectedFiles[0].fullName);
                }
            }
            else
            {
                doOpenFile(selectedFiles.fullName);
            }
            originalDoc = app.activeDocument;
            previewButton.enabled = originalDoc != null;
            zoomBox.text = getZoomLevel();
            previewZoomSlider.value = getZoomLevel();
        }
    };
    previewButton.onClick = function()
    {
        if(originalDoc != null)
        {
            var tempOutputBitDepth = outputBitDepth;
            outputBitDepth = 16;
            if(previewDoc != null)
            {
                previewDoc.close(SaveOptions.DONOTSAVECHANGES);
            }
            previewDoc = originalDoc.duplicate("HDR Preview");
            app.activeDocument = previewDoc;
            setZoomLevel(previewZoomSlider.value);
            convertTo32Bit();
            doHDRToning();
            outputBitDepth = tempOutputBitDepth;
            waitForRedraw();
            zoomBox.enabled = previewDoc != null;
            previewZoomSlider.enabled = previewDoc != null;
        }
    };
    zoomBox.onChange = function()
    {
        if(previewDoc != null)
        {
            previewZoomSlider.value = zoomBox.text;
            setZoomLevel(previewZoomSlider.value);
        }
    }
    previewZoomSlider.onChange = function()
    {
        if(previewDoc != null)
        {
            zoomBox.text = previewZoomSlider.value.toFixed(0);
            setZoomLevel(previewZoomSlider.value);
        }
    };
    
    updateSliders();
}


function createSliderControl(group,label,postLabel,min,max,round,value,onValueChanged)
{
    var ignoreChange = false;
    group.add("statictext", undefined, label);
    var slider = group.add("slider",undefined,value,min,max);
    slider.alignment = "fill";
    var box = group.add("edittext",undefined,value);
    box.characters = 6;
    group.add("statictext", undefined, postLabel);
    slider.onChange = function()
    {
        if(!ignoreChange)
        {
            ignoreChange = true;
            box.text = slider.value.toFixed(round);
            onValueChanged(slider.value);
            ignoreChange = false;
        }
    };
    box.onChange = function()
    {
        if(!ignoreChange)
        {
            ignoreChange = true;
            slider.value = box.text;
            onValueChanged(box.text);
            ignoreChange = false;
        }
    };
    return function(newValue)
    {
        slider.value = newValue;
        box.text = newValue.toFixed(round);
    };
}

//forces a redraw while a script dialog is active (for preview)
var waitForRedraw = function()
{
    var desc = new ActionDescriptor();
    desc.putEnumerated(charIDToTypeID("Stte"), charIDToTypeID("Stte"), charIDToTypeID("RdCm"));
    executeAction(charIDToTypeID("Wait"), desc, DialogModes.NO);
}

function getZoomLevel()
{
    var ref = new ActionReference();
    ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
    var desc = executeActionGet(ref);
    return Number(desc.getDouble(stringIDToTypeID('zoom'))*100).toFixed(1);
}

function setZoomLevel( zoom )
{
    if(zoom < 1 ) zoom =1;
    var ref = new ActionReference();
    ref.putEnumerated( charIDToTypeID("capp"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
    var getScrRes = executeActionGet(ref).getObjectValue(stringIDToTypeID('unitsPrefs')).getUnitDoubleValue(stringIDToTypeID('newDocPresetScreenResolution'))/72;
    var docRes = activeDocument.resolution;
    // CC UPDATE: Document.resizeImage() now takes an additional arg, amount (values: 0-100, describes: resampling)
    activeDocument.resizeImage( undefined, undefined, getScrRes/(zoom/100), ResampleMethod.NONE, 0 );
    var desc = new ActionDescriptor();
    ref = null;
    ref = new ActionReference();
    ref.putEnumerated( charIDToTypeID( "Mn  " ), charIDToTypeID( "MnIt" ), charIDToTypeID( 'PrnS' ) );
    desc.putReference( charIDToTypeID( "null" ), ref );
    executeAction( charIDToTypeID( "slct" ), desc, DialogModes.NO );
    activeDocument.resizeImage( undefined, undefined, docRes, ResampleMethod.NONE, 0 );	// also updated for CC
}

function ZeroPad(number,numZeros)
{
    var result = number.toString();
    while(result.length < numZeros)
    {
        result = "0" + result;
    }
    return result;
}

var getPresetFiles = function()
{
    var presetFolder = new Folder(app.path + "/Presets/HDR Toning");
    return presetFolder.getFiles("*.hdt");
}

var loadPreset = function(presetFile)
{
    if(presetFile == null)
    {
        presetFile = File.openDialog("Select Preset","*.hdt");
    }
    if(presetFile != null)
    {
        var tmpStr = new String();
        var binaryData = new Array();
        presetFile.encoding = "BINARY";
        presetFile.open('r');
        while(!presetFile.eof)
        {
            var ch = presetFile.readch();
            if ( ch.charCodeAt(0) == 0 ){
                tmpStr += ' ';
            }
            else {
                tmpStr += ch;
            }
            binaryData.push(ch.charCodeAt(0));
        }
        presetFile.close();
        if(binaryData.length >= 40)
        {
            // init start position for reading datas
            // start position for english version ( string "D e f a u l t" is in the preset file )
            var startPos = 38;
            if ( tmpStr.search ("P a r   d é f a u t") > -1 ){
                // start position for french preset file version ( string "P a r   d é f a u t" is in the preset file ) (==> + 6 bytes)
                startPos = 44;
            }
            // if your preset file can't be read, try this : open it in notepad to see the string "D e f a u l t" in your language and add the code here to set startPos to 38 + diff between the length of ("D e f a u l t") and length of ("D e f a u l t" in your language)
            var curvePointCount = getUInt16(binaryData, startPos);
            if(binaryData.length >= 104 + curvePointCount * 4)
            {
                var curvePointStr = "";
                for(var i = 0; i < curvePointCount; i++)
                {
                    curvePointStr += getUInt16(binaryData, startPos + 4 + i * 4) + "," + getUInt16(binaryData, startPos + 2 + i * 4) + ((i < curvePointCount - 1) ? "," : "");
                }
                hdrCurve = curvePointStr;
                
                hdrStrength =  getFloat32(binaryData,8);
                hdrRadius = getFloat32(binaryData, startPos + 10 + 5 * curvePointCount);
                hdrExposure = getFloat32(binaryData, startPos + 34 + 5 * curvePointCount);
                hdrSaturation = getFloat32(binaryData, startPos + 38 + 5 * curvePointCount);
                hdrDetail = getFloat32(binaryData, startPos + 42 + 5 * curvePointCount);
                hdrShadow = getFloat32(binaryData, startPos + 46 + 5 * curvePointCount);
                hdrHighlights = getFloat32(binaryData, startPos + 50 + 5 * curvePointCount);
                hdrGamma = getFloat32(binaryData, startPos + 54 + 5 * curvePointCount);
                hdrVibrance = getFloat32(binaryData, startPos + 58 + 5 * curvePointCount);
                hdrSmooth = getUInt16(binaryData, startPos + 62 + 5 * curvePointCount) != 0;
            }
            else
            {
                alert("Error Loading File", "Error", true);
            }
        }
        else
        {
            alert("Error Loading File", "Error", true);
        }
    }
}

function getUInt16(byteArray,offset)
{
    return byteArray[offset] * 0x100 + byteArray[offset + 1];
}

function getUInt32(byteArray,offset)
{
    return byteArray[offset] * 0x1000000 + byteArray[offset + 1] * 0x10000 + byteArray[offset + 2] * 0x100 + byteArray[offset + 3];
}

function getFloat32(byteArray,offset)
{
    var bytes = getUInt32(byteArray,offset);
    var sign = (bytes & 0x80000000) ? -1 : 1;
    var exponent = ((bytes >> 23) & 0xFF) - 127;
    var significand = (bytes & ~(-1 << 23));
    
    if (exponent == 128)
        return sign * ((significand) ? Number.NaN : Number.POSITIVE_INFINITY);
    
    if (exponent == -127) {
        if (significand == 0) return sign * 0.0;
        exponent = -126;
        significand /= (1 << 22);
    } else significand = (significand | (1 << 23)) / (1 << 23);
    
    return sign * significand * Math.pow(2, exponent);
}

main();

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 04, 2021 Mar 04, 2021

Copy link to clipboard

Copied

I just downloaded the Nik filters and they are not built for Photoshop automation, neither actions or scriptinglistener record anything.

Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

@Stephen_A_Marsh , okay thanks for your effort and help.

Although I find it very strange that Nik filters don't have a batch-option. I think I'm not the only one who does use it for multiple images. Hopefully some day they will make a batch-option available.


Likes

translate

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines