Copy link to clipboard
Copied
I've seen a number of different posts on these forums about trying to do this and I never really found a complete and suitable solution. It also has really annoyed me that adobe never implemented this (and it's not possible to do with actions because the action just runs a script and you can't record the HDR toning settings, the dialog always pops up, its in the script). So I finally had some time to get my hands dirty with ExtendScript and I have created a fully featured script complete with UI that allows batch creation of HDR tonned images in photoshop.
The code I got working from various snipets of code I found around the 'net and single stepping through the MergeToHDR.jsx script that is built into photoshop.
This has only been partially tested on photoshop CC on a mac. Hopefully I made it portable enough and it will work on windows and older versions. I don't think the MergeToHDR script has changed much in the last several versions (and that is mainly what I call to do the work) so I think it should work. If you have issues please let me know.
My motivation for this script was coming up with a way to do batch HDR toning for timelapse and so this script's features lean toward using it for that end. I really didn't want to buy another separate program for doing this like photomatix, when I knew it should at least be possible to do in photoshop.
I uploaded the script to github:
https://github.com/davidmilligan/PhotoshopBatchHDR/blob/master/Batch%20HDR.jsx
How to use:
Limitations:
You're welcome Adobe, you can send me a check at:
[REDACTED]
[REDACTED]
[REDACTED]
Please hit me up with comments/suggestions,
David
Copy link to clipboard
Copied
I wish Adobe will enable a preview functionality just like in Gaussian Blur filter.
Small 100% Preview window and big preview on the main window.
Copy link to clipboard
Copied
David, thank you!! I'm reinstalling PS this weekend and absolutely looking forward to trying this out!
Copy link to clipboard
Copied
Thank you for your script, it's very useful.
I just changed a few things :
The Checkboxes didn't work so I changed them :
alignCheckBox.onChanged by alignCheckBox.onClick
deghostCheckBox.onChanged by deghostCheckBox.onClick
smoothEdgesBox.onChange by smoothEdgesBox.onClick
And for best alignment, I changed :
mergeToHDR.mergeFilesToHDR( currentFileList, false, hdrDeghosting ? kMergeToHDRDeghostBest : kMergeToHDRDeghostOff );
by :
mergeToHDR.mergeFilesToHDR( currentFileList, mergeToHDR.useAlignment, hdrDeghosting ? kMergeToHDRDeghostBest : kMergeToHDRDeghostOff );
And lastly :
My language is french so I had to modify loadPreset function because my own preset file contains the string "P a r d é f a u t" in place of "D e f a u l t" so that the data is shifted.
Here is the code :
#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 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 = true;
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);
var currentFileList = new Array();
for(var index = 0; index < files.length; index++)
{
if((index % numberOfBrackets) == numberOfBrackets - 1)
{
var start = new Date();
progress.value = 100 * index / files.length;
currentFileList.push(files[index]);
if(userCanceled) break;
if(numberOfBrackets > 1)
{
statusText.text = "Merging files "+(index-numberOfBrackets+2)+" - "+(index+1)+" of "+files.length + 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 ? kMergeToHDRDeghostBest : kMergeToHDRDeghostOff );
statusText.text = "Toning files "+(index-numberOfBrackets+2)+" - "+(index+1)+" of "+files.length+ estTimeRemaining;
}
else
{
statusText.text = "Loading file "+(index+1)+" of "+files.length+ estTimeRemaining;
//otherwise just open the file
doOpenFile(files[index]);
statusText.text = "Toning file "+(index+1)+" of "+files.length+ estTimeRemaining;
}
progress.value = 100 * (index + numberOfBrackets / 2 ) / files.length;
if(userCanceled) break;
if(app.activeDocument != null)
{
//apply the actual tone mapping to the HDR image to get it back down to 8 bits
doHDRToning();
}
//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 "+files.length+ estTimeRemaining;
}
else
{
statusText.text = "Saving result "+(index+1)+" of "+files.length+ estTimeRemaining;
}
if(userCanceled) break;
doSaveFile(outputFolder.absoluteURI + "/" + outputFilename + ZeroPad(Math.round((index + 1)/numberOfBrackets), 5) );
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 * ((files.length - 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
{
activeDocument.saveAs(new File(filename), undefined, true /*Save As Copy*/, Extension.LOWERCASE /*Append Extention*/);
}
}
function doHDRToning()
{
//TODO: Reverse engineer the HDR Preset file format and allow user to select a preset file and create the ActionDescriptor from the file
//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 );
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' }\
deghostCheckBox: Checkbox { text: 'Deghost' }\
} \
}, \
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 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 deghostCheckBox = setupWindow.leftGroup.inputPanel.bracketGroup.deghostCheckBox;
//set default values
bracketBox.text = numberOfBrackets;
filterText.text = fileMask;
//mergeToHDR.useAlignment = true;
alignCheckBox.value = mergeToHDR.useAlignment;
deghostCheckBox.value = hdrDeghosting;
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", "PSD");
saveDropDown.selection = 0;
outputBitDepthDropDown.add("item", "8");
outputBitDepthDropDown.add("item", "16");
outputBitDepthDropDown.add("item", "32");
outputBitDepthDropDown.selection = 0;
//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;
}
okButton.enabled = sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists;
};
bracketBox.onChange = function() { numberOfBrackets = bracketBox.text; };
filterText.onChange = function() { fileMask = filterText.text; };
alignCheckBox.onClick = function() { mergeToHDR.useAlignment = alignCheckBox.value; };
deghostCheckBox.onClick = function() { hdrDeghosting = deghostCheckBox.value; };
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;
}
outputBitDepthDropDown.enabled = saveDropDown.selection.text != "JPEG";
outputBitDepthLabel.enabled = saveDropDown.selection.text != "JPEG";
};
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
}
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", function(){return true;}, 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;
activeDocument.resizeImage( undefined, undefined, getScrRes/(zoom/100), ResampleMethod.NONE );
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 );
}
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();
Copy link to clipboard
Copied
Thank you! Would you mind submitting those changes as a pull request via github so I can easily merge them? If not I can do it myself, just wanted you to have the credit in the repo. (Also, there was another pull request I just merged so there will be some slight differences since you made your change - you might want to do a diff of your changes against the version you changed and then apply the diff to the current version).
Copy link to clipboard
Copied
Hi,
Please do the merge yourself (I don't mind if you take credit for it).
Thanks!
Copy link to clipboard
Copied
You guys might also be interested in this other script I wrote for Bridge:
Copy link to clipboard
Copied
This script is fantastic for stuff shot on tripod. However, it appears that the 'Align' & "De-ghost' fucntions do not seem to work. I shot some hand-held HDR stuff and the ghosting and alignment issues are very present. When I process the shots individually with 'mergetoHDRpro' they turn out fine. I replaced the few lines in the script above with the hope that it may fix this issue but alas, no deal. This isn't a massive hassle as it's only 20 or so HDR shots so not a big deal to process them individually.
For timelpase stuff this is beyond awesome. The ability to process with Camera Raw and get some realistic HDR toning in high contrast situations is something I've been looking for a very long time!
Thanks for taking the time to write this script!
Copy link to clipboard
Copied
Now I'm getting an error on line 121 " if(app.activeDocument != null) ". Just reverted the script back to the original and it works. Not sure what's going on.
Fixed! I replaced the whole section around line 121. Somthing to do with bracket length instead of number of brackets. (Not handy with code)
Copy link to clipboard
Copied
Hi Lucas,
I haven't got any problem with 'Align' & "De-ghost' applied on hand-held photos, are you sure to use the latest version (get it again from GITHUB).
You may get an error "if(app.activeDocument != null)" if you have a wrong number of files in source directory or your file mask is "*" and you have a text file in the source folder.
Copy link to clipboard
Copied
Hi David,
I have done a small change from the latest version again :
To have the same behavior than "sourceBrowse.onClick" (copy to outputBox), I changed this :
//event handlers
sourceBox.onChange = function()
{
sourceFolder = new Folder(sourceBox.text);
okButton.enabled = sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists;
};
by :
//event handlers
sourceBox.onChange = function()
{
sourceFolder = new Folder(sourceBox.text);
if(sourceFolder != null)
{
if (outputFolder == null)
{
outputFolder = sourceFolder;
outputBox.text = outputFolder.fullName;
}
}
okButton.enabled = sourceFolder != null && outputFolder != null && sourceFolder.exists && outputFolder.exists;
};
and, for our friend "Lucas", line 136, I changed this : (it's more "user friendly")
if(app.activeDocument != null)
{
//apply the actual tone mapping to the HDR image to get it back down to 8 bits
doHDRToning();
}
by :
try{
if(app.activeDocument != null)
{
//apply the actual tone mapping to the HDR image to get it back down to 8 bits
doHDRToning();
}
}
catch(e){
alert("Something is wrong : check files mask and/or files in "+sourceFolder.fullName+" (numbers could be incorrect).");
return;
}
Copy link to clipboard
Copied
David,
The script works great, I process 25 to 30 7 frame images at a time and 80% of the Tiff are just what I expect, If I could get the program to choose 0.0 Exposure value for remove ghost in 32-bit mode All the images would be perfect. I have tried a few thing but just seem to make matters worse. any and all help is greatly appreciated.
Matt
Copy link to clipboard
Copied
@Matthew M. Ross
When it comes to floating point data (32 bit), there's really no difference in what you set as the "exposure". Floating point means that all brightness ranges have the same weight. When exporting as 32-bit float the image may appear darker depending on how the program you are viewing it with decides to deal with the extra bit depth, but the data is still there, you'll have to tone it or stretch it how you like it to go back down to the low, 8 bit depth of a screen (i.e. there's a lot more data in a 32bit image than you can 'see' on an 8 bit display). 32bit float output is simply meant to be an intermediate format if you only want to use this script to merge, and you want to tone with something else.
If you want to tone with Photoshop, then set the output to 8 or 16 bit and use the tonning settings. If you want to tone with something else (like ACR), and just use the script to merge, export as 32-bit TIFF, and use whatever tool you'd like to use on the resulting TIFF seqeunce.
Copy link to clipboard
Copied
Thank you for the script, it does exactly what I need! But I have a problem with the 32bits files created with the BatchHDR, they are completely wrong. I'm merging 11 CR2 files together. Any idea?
And is there a way to start that script from Bridge?
Thanks!
Copy link to clipboard
Copied
@Chafouin,
Try it now, I might have fixed it. It skips the toning step if outputing 32 bit format.
@All,
I have implemented two new output formats, OpenEXR (16-bit floating point) and Radiance (RGBE). Toning will not be applied if you use either of these formats.
Copy link to clipboard
Copied
Yes it works now, thanks a lot
Copy link to clipboard
Copied
Hi,
thanks a lot for the script, ti's exactly what I need. Unfortunately, when I run it on a folder that contains three images per bracket, the script doesn't go in the order of the filenames, but starts with the second and after that takes the last two and the first. How is the order determined in which the script picks the files?
Thanks a lot,
C
Copy link to clipboard
Copied
someone had a very similar problem recently, try the most recent version, I just added a fix, let me know if it works
Copy link to clipboard
Copied
I'm having some major issues... whenever I try to do a TL with 3 brackets... I keep getting an error message after the first one saying "Error: No such element Check number of files in source folder"
Anybody have a solution to this? As you can imagine it makes the script unusable for me.
Copy link to clipboard
Copied
Did you try using the file filter? I'm thinking I might make this default to something and be required.
Copy link to clipboard
Copied
I fixed it last night. That was it. Thanks though.
Austin Kelm
Cinematographer - Wake Studios
wakestudiosjax.com
Copy link to clipboard
Copied
Hello, all this is way over my head, coding wise. I copy and pasted the code to a text file and then changed the ext. to jsx and put it in the folder but when I try and run it in photoshop i get this
Line: 1
-> {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf210
as an error. I am working on a mac and the newest version of photoshop. Any Help? I also is under the scripts menu not the not the automate.
Copy link to clipboard
Copied
Howdy!
So I've installed your script and it worked only one time but now I'm being given the error "Error: No such element, Check number of files in source folder"
Thus I've checked and I find that I have (smaller folder amount) 21 images, the flat image, a stop above and a stop below. But then I check the number of files (under more info on the folder) and find that there are 22 files and I can't seem to figure out why?
Have there been any new updates to this script? I really don't want to have to go through all 1000+ photos for my HDR timelapse.
Many thanks in advance and really great add in to PS!
-Hawk
Copy link to clipboard
Copied
It would be so so so amazing if this script worked for me. But I am getting the same error as a few people in this thread: "Error: No such element, Check number of files in source folder"
I have 420 images in my folder. My "Number of Brackets" is set to 3. 420 divided by 3 is 140 – an even number.
So i'm curious why this script is behaving as if I have an indivisible number of images in my folder?
I tried the "File Filter" field, but couldn't get it to work. admiralaelove said it helped his issue when he had the same error. But not sure why.
If anyone has a clue I would be very grateful. This script is so very helpful. If only it worked for me!
Thanks,
Matt
Copy link to clipboard
Copied
PS – Download an image of my error message here: https://dl.dropboxusercontent.com/u/7424730/Screen%20Shot%202015-02-18%20at%209.09.23%20PM.png