/** * InDesign ExtendScript * Split OR Dissolve the currently selected graphic frame. * - Split: regular grid split (rows/cols/gaps) keeping image in same absolute position. * - Dissolve: creates particle tiles (size-based) and dissolves them with wind/gravity/strength. * * Select ONE graphic frame containing a placed image, then run. * * By: Mohammad Sadeea - sadeea.de */ (function () { if (app.documents.length === 0) { alert("No document open."); return; } if (app.selection.length !== 1) { alert("Select exactly ONE graphic frame that contains a placed image."); return; } var frame = app.selection[0]; if (!frame) { alert("The selected item must be a frame."); return; } // ---------- helpers ---------- function toInt(v, label) { var n = parseInt(v, 10); if (isNaN(n) || n < 1) throw new Error(label + " must be an integer ≥ 1."); return n; } function toNumber(v, label) { var n = Number(v); if (isNaN(n)) throw new Error(label + " must be a number."); return n; } function toPoints(v, label) { var s = (v + "").replace(/^\s+|\s+$/g, ""); if (s === "") return 0; try { if (!/[a-zA-Z]/.test(s)) s = s + " pt"; return UnitValue(s).as("pt"); } catch (e) { throw new Error(label + " is not a valid measurement: " + v); } } function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); } function rand() { return Math.random(); } function randRange(a, b) { return a + (b - a) * rand(); } // ---------- UI ---------- var w = new Window("dialog", "Split / Dissolve Graphic Frame"); w.alignChildren = "fill"; var modeP = w.add("panel", undefined, "Mode"); modeP.orientation = "row"; modeP.alignChildren = "left"; modeP.add("statictext", undefined, "Mode:"); var modeDd = modeP.add("dropdownlist", undefined, ["Split Grid", "Dissolve"]); modeDd.selection = 0; // Split options var splitP = w.add("panel", undefined, "Split options"); splitP.orientation = "column"; splitP.alignChildren = "left"; var rowGrp = splitP.add("group"); rowGrp.add("statictext", undefined, "Rows:"); var rowsEt = rowGrp.add("edittext", undefined, "2"); rowsEt.characters = 8; var colGrp = splitP.add("group"); colGrp.add("statictext", undefined, "Columns:"); var colsEt = colGrp.add("edittext", undefined, "2"); colsEt.characters = 8; var gapXGrp = splitP.add("group"); gapXGrp.add("statictext", undefined, "Horizontal gap:"); var gapXEt = gapXGrp.add("edittext", undefined, "0"); gapXEt.characters = 12; gapXGrp.add("statictext", undefined, "(e.g. 2 mm, 6 pt, 0)"); var gapYGrp = splitP.add("group"); gapYGrp.add("statictext", undefined, "Vertical gap:"); var gapYEt = gapYGrp.add("edittext", undefined, "0"); gapYEt.characters = 12; gapYGrp.add("statictext", undefined, "(e.g. 2 mm, 6 pt, 0)"); // Dissolve options var disP = w.add("panel", undefined, "Dissolve options"); disP.orientation = "column"; disP.alignChildren = "left"; var strengthGrp = disP.add("group"); strengthGrp.add("statictext", undefined, "Strength (0–100):"); var strengthEt = strengthGrp.add("edittext", undefined, "70"); strengthEt.characters = 6; var windGrp = disP.add("group"); windGrp.add("statictext", undefined, "Wind (X drift):"); var windEt = windGrp.add("edittext", undefined, "40 pt"); windEt.characters = 12; windGrp.add("statictext", undefined, "(+ right / - left)"); var gravGrp = disP.add("group"); gravGrp.add("statictext", undefined, "Gravity (Y drift):"); var gravEt = gravGrp.add("edittext", undefined, "10 pt"); gravEt.characters = 12; gravGrp.add("statictext", undefined, "(+ down / - up)"); var sizeGrp = disP.add("group"); sizeGrp.add("statictext", undefined, "Particle size:"); var sizeEt = sizeGrp.add("edittext", undefined, "6 pt"); sizeEt.characters = 12; sizeGrp.add("statictext", undefined, "(bigger = faster)"); var disGapGrp = disP.add("group"); disGapGrp.add("statictext", undefined, "Particle gap:"); var disGapEt = disGapGrp.add("edittext", undefined, "0"); disGapEt.characters = 12; // shrink dissolved tiles option var shrinkGrp = disP.add("group"); shrinkGrp.add("statictext", undefined, "Shrink dissolved tiles (0–100%):"); var shrinkEt = shrinkGrp.add("edittext", undefined, "60"); shrinkEt.characters = 6; shrinkGrp.add("statictext", undefined, "(0=none, 100=shrink a lot)"); var fadeChk = disP.add("checkbox", undefined, "Fade particles as they dissolve"); fadeChk.value = true; var keepOrigChk = w.add("checkbox", undefined, "Keep original frame"); keepOrigChk.value = false; function syncPanels() { var isSplit = (modeDd.selection.index === 0); splitP.enabled = isSplit; disP.enabled = !isSplit; } modeDd.onChange = syncPanels; syncPanels(); var btns = w.add("group"); btns.alignment = "right"; btns.add("button", undefined, "Cancel", { name: "cancel" }); btns.add("button", undefined, "OK", { name: "ok" }); if (w.show() !== 1) return; // ---------- read options ---------- var mode = modeDd.selection.index; // 0 split, 1 dissolve var rows, cols, gapX, gapY; var strength, wind, gravity, pSize, pGap, doFade, shrinkAmt; try { if (mode === 0) { rows = toInt(rowsEt.text, "Rows"); cols = toInt(colsEt.text, "Columns"); gapX = toPoints(gapXEt.text, "Horizontal gap"); gapY = toPoints(gapYEt.text, "Vertical gap"); } else { strength = clamp(toNumber(strengthEt.text, "Strength"), 0, 100) / 100; // 0..1 wind = toPoints(windEt.text, "Wind"); gravity = toPoints(gravEt.text, "Gravity"); pSize = toPoints(sizeEt.text, "Particle size"); pGap = toPoints(disGapEt.text, "Particle gap"); doFade = !!fadeChk.value; // NEW: 0..1 shrinkAmt = clamp(toNumber(shrinkEt.text, "Shrink dissolved tiles"), 0, 100) / 100; if (pSize <= 0) throw new Error("Particle size must be > 0."); } } catch (err) { alert(err.message); return; } // ---------- main ---------- app.doScript(function () { var oldRedraw = app.scriptPreferences.enableRedraw; var oldUI = app.scriptPreferences.userInteractionLevel; app.scriptPreferences.enableRedraw = false; app.scriptPreferences.userInteractionLevel = UserInteractionLevels.NEVER_INTERACT; var gb = frame.geometricBounds; // [top,left,bottom,right] var top = gb[0], left = gb[1], bottom = gb[2], right = gb[3]; var totalW = right - left; var totalH = bottom - top; function makeCell(geoBounds) { var p = frame.parent ? frame.parent : null; var dup = frame.duplicate(p); dup.itemLayer = frame.itemLayer; dup.geometricBounds = geoBounds; return dup; } // ----- Split mode ----- if (mode === 0) { var usableW = totalW - (cols - 1) * gapX; var usableH = totalH - (rows - 1) * gapY; if (usableW <= 0 || usableH <= 0) throw new Error("Gaps are too large for the frame size."); var cellW = usableW / cols; var cellH = usableH / rows; for (var r = 0; r < rows; r++) { for (var c = 0; c < cols; c++) { var cellTop = top + r * (cellH + gapY); var cellLeft = left + c * (cellW + gapX); makeCell([cellTop, cellLeft, cellTop + cellH, cellLeft + cellW]); } } if (!keepOrigChk.value) frame.remove(); return; } // ----- Dissolve mode ----- // Determine cols/rows from particle size var stepX = pSize + pGap; var stepY = pSize + pGap; var colsD = Math.max(1, Math.floor((totalW + pGap) / stepX)); var rowsD = Math.max(1, Math.floor((totalH + pGap) / stepY)); // Recompute actual cell size to fit cleanly across the frame var usableWD = totalW - (colsD - 1) * pGap; var usableHD = totalH - (rowsD - 1) * pGap; var cellWD = usableWD / colsD; var cellHD = usableHD / rowsD; var count = colsD * rowsD; if (count > 2000) { alert("Warning: This will create about " + count + " particles and may be slow.\nTry a larger Particle size for better performance."); return; } for (var rr = 0; rr < rowsD; rr++) { for (var cc = 0; cc < colsD; cc++) { var xNorm = (colsD === 1) ? 0 : (cc / (colsD - 1)); // 0..1 left->right var dissolveProb = clamp(Math.pow(xNorm, 1.4) * strength, 0, 1); var cellTopD = top + rr * (cellHD + pGap); var cellLeftD = left + cc * (cellWD + pGap); var tile = makeCell([cellTopD, cellLeftD, cellTopD + cellHD, cellLeftD + cellWD]); if (rand() < dissolveProb) { // Dissolved tiles: drift with wind/gravity, plus randomness var mag = dissolveProb; // 0..1 var dx = wind * mag * randRange(0.3, 1.2) + randRange(-0.35, 0.35) * wind * mag; var dy = gravity * mag * randRange(0.3, 1.2) + randRange(-0.35, 0.35) * gravity * mag; tile.move(undefined, [dx, dy]); // ------------------------------------------------------------ // Shrink increases "downwind": farther downwind = smaller. // Works for wind positive (right) OR negative (left). // Tiles that drift upwind (against wind) don't get wind-shrunk. // ------------------------------------------------------------ // Estimate max possible downwind drift (based on how dx is computed) var windAbs = Math.abs(wind); var maxDx = windAbs * strength * 1.55; // 1.2 + 0.35 worst-case multiplier // Downwind projection: normalize dx so "downwind" is positive. // If wind>0: downwind is +dx. If wind<0: downwind is -dx. var downwind = 0; if (windAbs > 0.0001 && maxDx > 0.0001) { downwind = (wind >= 0) ? dx : (-dx); downwind = clamp(downwind / maxDx, 0, 1); // 0..1 } else { // No wind: fall back to dissolve amount (keeps behavior sensible) downwind = mag; } // Base "speckle" shrink (your original behavior, kept) var speckle = 1 - (mag * randRange(0.10, 0.55)); // 0.45..0.90-ish // wind-based shrink (0..shrinkAmt), scaled by how far downwind it went // shrinkAmt is 0..1 from UI var windShrink = 1 - (shrinkAmt * downwind); windShrink = clamp(windShrink, 0.05, 1); // never below 5% var finalScale = shrinkAmt > 0 ? speckle * windShrink : 1; try { tile.resize( CoordinateSpaces.PASTEBOARD_COORDINATES, AnchorPoint.CENTER_ANCHOR, ResizeMethods.MULTIPLYING_CURRENT_DIMENSIONS_BY, [finalScale, finalScale] ); } catch (e) { } // optional fade out if (doFade) { try { tile.transparencySettings.blendingSettings.opacity = Math.round(100 * (1 - mag * randRange(0.3, 0.95))); } catch (e2) { } } } else { // Solid tiles... if (doFade) { try { tile.transparencySettings.blendingSettings.opacity = 100; } catch (e3) { } } } } } if (!keepOrigChk.value) frame.remove(); app.scriptPreferences.enableRedraw = oldRedraw; app.scriptPreferences.userInteractionLevel = oldUI; }, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, "Split/Dissolve Frame"); })();