init
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# Photoshop ExtendScript Dataset Generator
|
||||
|
||||
This folder contains JSX scripts to generate systematic layer-effect ground-truth images in Adobe Photoshop.
|
||||
|
||||
## Files
|
||||
|
||||
- `generate_inner_shadow_dataset.jsx` — Starter script for Inner Shadow.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open Adobe Photoshop.
|
||||
2. Choose `File > Scripts > Browse...`.
|
||||
3. Select `generate_inner_shadow_dataset.jsx`.
|
||||
4. Choose an output folder when prompted.
|
||||
|
||||
Photoshop will create a new document for each parameter combination, draw a filled black rectangle, apply the Inner Shadow effect, and export a transparent PNG.
|
||||
|
||||
## Current parameter grid (short version)
|
||||
|
||||
| Parameter | Values |
|
||||
|---|---|
|
||||
| angle | 0, 90, 180, 270 |
|
||||
| distance | 5, 15, 30 |
|
||||
| size | 10, 30 |
|
||||
| choke | 0, 20 |
|
||||
| fx opacity | 75, 100 |
|
||||
| fx blend mode | Normal, Multiply |
|
||||
|
||||
Total images: `4 × 3 × 2 × 2 × 2 × 2 = 192`.
|
||||
|
||||
## Notes
|
||||
|
||||
- Blend mode keys use Photoshop's 4-character codes (`Nrml`, `Mltp`).
|
||||
- The output PNGs have transparent backgrounds, so MAE comparisons can ignore white reference pixels if needed.
|
||||
- Opacity is passed as 0-100 (e.g. 75 means 75%).
|
||||
- If a combination fails, the script alerts and continues with the next one.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Add layer blend mode, layer opacity, and fill opacity variations.
|
||||
- Extend to Drop Shadow, Outer Glow, Inner Glow, Stroke, Bevel & Emboss, Color Overlay.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Photoshop Batch PNG Exporter
|
||||
|
||||
This script batch-exports PSD files to transparent PNGs.
|
||||
|
||||
## Files
|
||||
|
||||
- `export_psd_folder_to_png.jsx`
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open Adobe Photoshop.
|
||||
2. Choose `File > Scripts > Browse...`.
|
||||
3. Select `export_psd_folder_to_png.jsx`.
|
||||
4. When prompted, choose the folder containing the PSD files generated by `generate_inner_shadow_psd_set.rs`.
|
||||
5. The script saves one PNG next to each PSD.
|
||||
|
||||
## Notes
|
||||
|
||||
- Existing PNGs are skipped, so you can re-run safely.
|
||||
- If any PSD fails to open or export, the script logs it and continues.
|
||||
- Output PNGs use transparent background.
|
||||
|
||||
## Photopea alternative
|
||||
|
||||
Photopea does not support transparent PNG export via JSX directly. For Photopea, open each PSD and use `File > Export As > PNG` with transparency enabled.
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* export_psd_folder_to_png.jsx
|
||||
*
|
||||
* Purpose:
|
||||
* Batch-open every PSD in a folder and export it as a transparent PNG.
|
||||
*
|
||||
* Logic & Workflow:
|
||||
* 1. Prompts the user to select a folder containing PSD files.
|
||||
* 2. Iterates over all *.psd files in that folder.
|
||||
* 3. Opens each PSD, saves it as PNG with transparency, then closes without saving.
|
||||
*
|
||||
* Arguments:
|
||||
* None (interactive). User selects input folder via dialog.
|
||||
*
|
||||
* Side Effects:
|
||||
* - Opens and closes many Photoshop documents.
|
||||
* - Writes PNG files next to the original PSDs.
|
||||
*/
|
||||
|
||||
var inFolder = Folder.selectDialog("Select folder containing PSD files");
|
||||
if (!inFolder) {
|
||||
alert("No folder selected. Exiting.");
|
||||
throw new Error("No folder");
|
||||
}
|
||||
|
||||
var psdFiles = inFolder.getFiles("*.psd");
|
||||
if (psdFiles.length === 0) {
|
||||
alert("No PSD files found in " + inFolder.fsName);
|
||||
throw new Error("No PSD files");
|
||||
}
|
||||
|
||||
var pngOptions = new PNGSaveOptions();
|
||||
pngOptions.compression = 6;
|
||||
pngOptions.interlaced = false;
|
||||
|
||||
for (var i = 0; i < psdFiles.length; i++) {
|
||||
var psdFile = psdFiles[i];
|
||||
var baseName = psdFile.name.replace(/\.psd$/i, "");
|
||||
var pngFile = new File(inFolder + "/" + baseName + ".png");
|
||||
|
||||
if (pngFile.exists) {
|
||||
$.writeln("Skipping existing: " + pngFile.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
var doc = app.open(psdFile);
|
||||
doc.saveAs(pngFile, pngOptions, true, Extension.LOWERCASE);
|
||||
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||
$.writeln("Exported: " + pngFile.name);
|
||||
} catch (e) {
|
||||
$.writeln("Error processing " + psdFile.name + ": " + e);
|
||||
alert("Error processing " + psdFile.name + ":\n" + e);
|
||||
}
|
||||
}
|
||||
|
||||
alert("Done. Processed " + psdFiles.length + " PSD files.");
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* export_psd_folder_to_png.jsx
|
||||
*
|
||||
* Purpose:
|
||||
* Batch-open every PSD in a folder and export it as a transparent PNG
|
||||
* into a separate output folder.
|
||||
*
|
||||
* Logic & Workflow:
|
||||
* 1. Prompts user for input PSD folder.
|
||||
* 2. Prompts user for output PNG folder (default: input + "_png" suffix).
|
||||
* 3. Iterates over all *.psd files.
|
||||
* 4. Opens each PSD, saves as PNG with transparency, closes without saving.
|
||||
*
|
||||
* Arguments:
|
||||
* None (interactive).
|
||||
*
|
||||
* Side Effects:
|
||||
* - Opens and closes many Photoshop documents.
|
||||
* - Writes PNG files into the output folder.
|
||||
*/
|
||||
|
||||
var inFolder = Folder.selectDialog("Select PSD input folder");
|
||||
if (!inFolder) {
|
||||
alert("No folder selected. Exiting.");
|
||||
throw new Error("No folder");
|
||||
}
|
||||
|
||||
var defaultOut = new Folder(inFolder.parent.fsName + "/" + inFolder.name + "_png");
|
||||
var outFolder = Folder.selectDialog("Select PNG output folder", defaultOut);
|
||||
if (!outFolder) {
|
||||
outFolder = defaultOut;
|
||||
}
|
||||
if (!outFolder.exists) {
|
||||
outFolder.create();
|
||||
}
|
||||
|
||||
var psdFiles = inFolder.getFiles("*.psd");
|
||||
if (psdFiles.length === 0) {
|
||||
alert("No PSD files found in " + inFolder.fsName);
|
||||
throw new Error("No PSD files");
|
||||
}
|
||||
|
||||
var pngOptions = new PNGSaveOptions();
|
||||
pngOptions.compression = 6;
|
||||
pngOptions.interlaced = false;
|
||||
|
||||
for (var i = 0; i < psdFiles.length; i++) {
|
||||
var psdFile = psdFiles[i];
|
||||
var baseName = psdFile.name.replace(/\.psd$/i, "");
|
||||
var pngFile = new File(outFolder.fsName + "/" + baseName + ".png");
|
||||
|
||||
if (pngFile.exists) {
|
||||
$.writeln("Skipping existing: " + pngFile.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
var doc = app.open(psdFile);
|
||||
// Ensure no background layer blocks transparency
|
||||
if (doc.backgroundLayer) {
|
||||
doc.activeLayer = doc.backgroundLayer;
|
||||
doc.activeLayer.remove();
|
||||
}
|
||||
doc.trim(TrimType.TRANSPARENT);
|
||||
doc.saveAs(pngFile, pngOptions, true, Extension.LOWERCASE);
|
||||
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||
$.writeln("Exported: " + pngFile.name);
|
||||
} catch (e) {
|
||||
$.writeln("Error processing " + psdFile.name + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
alert("Done. Processed " + psdFiles.length + " PSD files.");
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* generate_inner_shadow_dataset.jsx
|
||||
*
|
||||
* Purpose:
|
||||
* Generate a systematic Inner Shadow effect dataset in Adobe Photoshop.
|
||||
*
|
||||
* Logic & Workflow:
|
||||
* 1. Creates a new transparent RGB document.
|
||||
* 2. Adds a raster layer and fills a centered rectangle with black.
|
||||
* 3. Applies one Inner Shadow effect via Photoshop ActionDescriptor.
|
||||
* 4. Saves the result as a transparent PNG.
|
||||
*
|
||||
* Arguments:
|
||||
* None (interactive). The user selects an output folder via dialog.
|
||||
*
|
||||
* Side Effects:
|
||||
* - Creates and closes many temporary Photoshop documents.
|
||||
* - Writes PNG files to the selected folder.
|
||||
*/
|
||||
|
||||
var outFolder = Folder.selectDialog("Select output folder for Inner Shadow dataset");
|
||||
if (!outFolder) {
|
||||
alert("No output folder selected. Exiting.");
|
||||
throw new Error("No output folder");
|
||||
}
|
||||
|
||||
var angles = [0, 90, 180, 270];
|
||||
var distances = [5, 15, 30];
|
||||
var sizes = [10, 30];
|
||||
var chokes = [0, 20];
|
||||
var fxOpacities = [75, 100];
|
||||
var fxBlendModes = [
|
||||
{ key: "Nrml", name: "normal" },
|
||||
{ key: "Mltp", name: "multiply" }
|
||||
];
|
||||
|
||||
function pxValue(v) {
|
||||
if (v === undefined || v === null) return 0;
|
||||
if (typeof v === "number") return v;
|
||||
if (typeof v.as === "function") return v.as("px");
|
||||
if (typeof v.value !== "undefined") return v.value;
|
||||
return Number(v);
|
||||
}
|
||||
|
||||
function createDocument(name, width, height) {
|
||||
try {
|
||||
return app.documents.add(width, height, 72, name, NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
|
||||
} catch (e) {
|
||||
return app.documents.add(width, height, 72, name, NewDocumentMode.RGB);
|
||||
}
|
||||
}
|
||||
|
||||
function addBlackRectangle(doc) {
|
||||
var w = pxValue(doc.width);
|
||||
var h = pxValue(doc.height);
|
||||
var margin = Math.round(Math.min(w, h) * 0.25);
|
||||
|
||||
var layer = doc.artLayers.add();
|
||||
layer.name = "shape";
|
||||
|
||||
var region = [
|
||||
[margin, margin],
|
||||
[w - margin, margin],
|
||||
[w - margin, h - margin],
|
||||
[margin, h - margin]
|
||||
];
|
||||
|
||||
doc.selection.select(region);
|
||||
|
||||
var black = new SolidColor();
|
||||
black.rgb.red = 0;
|
||||
black.rgb.green = 0;
|
||||
black.rgb.blue = 0;
|
||||
|
||||
doc.selection.fill(black);
|
||||
doc.selection.deselect();
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
function blendModeTypeID(mode) {
|
||||
try { return charIDToTypeID(mode); }
|
||||
catch (e) { return stringIDToTypeID(mode); }
|
||||
}
|
||||
|
||||
function applyInnerShadow(params) {
|
||||
var desc = new ActionDescriptor();
|
||||
|
||||
var ref = new ActionReference();
|
||||
ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("Lefx"));
|
||||
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
|
||||
desc.putReference(charIDToTypeID("null"), ref);
|
||||
|
||||
var fxDesc = new ActionDescriptor();
|
||||
|
||||
var isDesc = new ActionDescriptor();
|
||||
isDesc.putBoolean(charIDToTypeID("enab"), true);
|
||||
isDesc.putBoolean(charIDToTypeID("present"), true);
|
||||
isDesc.putBoolean(charIDToTypeID("showInDialog"), true);
|
||||
|
||||
isDesc.putEnumerated(charIDToTypeID("Blnd"), charIDToTypeID("BlnM"), blendModeTypeID(params.blendMode));
|
||||
|
||||
var clr = new ActionDescriptor();
|
||||
clr.putDouble(charIDToTypeID("Rd "), 0);
|
||||
clr.putDouble(charIDToTypeID("Grn "), 0);
|
||||
clr.putDouble(charIDToTypeID("Bl "), 0);
|
||||
isDesc.putObject(charIDToTypeID("Clr "), charIDToTypeID("RGBC"), clr);
|
||||
|
||||
isDesc.putInteger(charIDToTypeID("Opct"), params.opacity);
|
||||
isDesc.putInteger(charIDToTypeID("lagl"), params.angle);
|
||||
isDesc.putBoolean(charIDToTypeID("uglg"), false);
|
||||
isDesc.putInteger(charIDToTypeID("Dstn"), params.distance);
|
||||
isDesc.putInteger(charIDToTypeID("Ckmt"), params.choke);
|
||||
isDesc.putInteger(charIDToTypeID("blur"), params.size);
|
||||
isDesc.putInteger(charIDToTypeID("Nose"), 0);
|
||||
|
||||
fxDesc.putObject(charIDToTypeID("IrSh"), charIDToTypeID("IrSh"), isDesc);
|
||||
desc.putObject(charIDToTypeID("T "), charIDToTypeID("Lefx"), fxDesc);
|
||||
|
||||
executeAction(charIDToTypeID("setd"), desc, DialogModes.NO);
|
||||
}
|
||||
|
||||
function saveTransparentPng(doc, file) {
|
||||
var pngOptions = new PNGSaveOptions();
|
||||
pngOptions.compression = 6;
|
||||
pngOptions.interlaced = false;
|
||||
doc.saveAs(file, pngOptions, true, Extension.LOWERCASE);
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var errors = 0;
|
||||
var total = angles.length * distances.length * sizes.length * chokes.length * fxOpacities.length * fxBlendModes.length;
|
||||
|
||||
for (var a = 0; a < angles.length; a++) {
|
||||
for (var d = 0; d < distances.length; d++) {
|
||||
for (var s = 0; s < sizes.length; s++) {
|
||||
for (var c = 0; c < chokes.length; c++) {
|
||||
for (var o = 0; o < fxOpacities.length; o++) {
|
||||
for (var b = 0; b < fxBlendModes.length; b++) {
|
||||
var params = {
|
||||
angle: angles[a],
|
||||
distance: distances[d],
|
||||
size: sizes[s],
|
||||
choke: chokes[c],
|
||||
opacity: fxOpacities[o],
|
||||
blendMode: fxBlendModes[b].key
|
||||
};
|
||||
|
||||
var fileName = "is_" +
|
||||
"a" + params.angle + "_" +
|
||||
"d" + params.distance + "_" +
|
||||
"s" + params.size + "_" +
|
||||
"c" + params.choke + "_" +
|
||||
"op" + params.opacity + "_" +
|
||||
"bm" + fxBlendModes[b].name +
|
||||
".png";
|
||||
|
||||
var file = new File(outFolder + "/" + fileName);
|
||||
if (file.exists) { count++; continue; }
|
||||
|
||||
var doc = createDocument("inner_shadow_test", 256, 256);
|
||||
var layer = addBlackRectangle(doc);
|
||||
|
||||
try {
|
||||
applyInnerShadow(params);
|
||||
saveTransparentPng(doc, file);
|
||||
} catch (e) {
|
||||
$.writeln("Error: " + fileName + " — " + e);
|
||||
errors++;
|
||||
} finally {
|
||||
doc.close(SaveOptions.DONOTSAVECHANGES);
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count % 10 === 0) {
|
||||
$.writeln("Progress: " + count + " / " + total + " (errors: " + errors + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alert("Done. Generated " + count + " images, " + errors + " errors.\n" + outFolder.fsName);
|
||||
Reference in New Issue
Block a user