Mini Kabibi Habibi

Current Path : C:/Program Files/Adobe/Adobe Photoshop 2025/Presets/Scripts/
Upload File :
Current File : C:/Program Files/Adobe/Adobe Photoshop 2025/Presets/Scripts/Delete All Empty Layers.jsx

// Copyright: 2018 Adobe Systems, Inc. All rights reserved.
// Completely rewritten by Jaroslav Bereza <http://bereza.cz> previous script written by Naoki Hada
// Visit http://bereza.cz/ps for more scripts
/*
@@@BUILDINFO@@@ Delete All Empty Layers.jsx 2.0.0.0
*/
/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING
<javascriptresource>
<name>$$$/JavaScripts/DeleteAllEmptyLayers/Menu=Delete All Empty Layers</name>
<category>Delete</category>
<enableinfo>true</enableinfo>
<eventid>a0754df2-9c60-4b64-a940-6a2bb1102652</eventid>
<terminology><![CDATA[<< /Version 1 
                         /Events << 
                          /a0754df2-9c60-4b64-a940-6a2bb1102652 [($$$/JavaScripts/DeleteAllEmptyLayers/Menu=Delete All Empty Layers) /noDirectParam <<
                          >>] 
                         >> 
                      >> ]]></terminology>
</javascriptresource>
// END__HARVEST_EXCEPTION_ZSTRING
*/
// enable double clicking from the 
// Macintosh Finder or the Windows Explorer
#target Photoshop
// Make Photoshop the frontmost application
app.bringToFront();
// debug level: 0-2 (0:disable, 1:break on error, 2:break at beginning)
// $.level = 2;
// debugger; // launch debugger on next line
/*
    KNOWN ISSUES:
    - if you make set visibility of clipped masks to hidden and you run "undo" command then these layers stay hidden
*/
/////////////////////////
// SETUP
/////////////////////////
// all the strings that need localized
var strDeleteAllEmptyLayersHistoryStepName = localize("$$$/JavaScripts/DeleteAllEmptyLayers/Menu=Delete All Empty Layers");
/////////////////////////
// MAIN
/////////////////////////
var doc;  // remember the document. But we do it later because we first need make sure that there are documents
var numberOfLayers;
var backgroundCounter;
var isCancelled = false;
// caching precalculated typeID numbers for saving a bit miliseconds and nicer code
var TID = {
    property: charIDToTypeID("Prpr"),
    bounds: stringIDToTypeID("bounds"),
    layer: charIDToTypeID("Lyr "),
    top: stringIDToTypeID('top'),
    bottom: stringIDToTypeID('bottom'),
    left: stringIDToTypeID('left'),
    right: stringIDToTypeID('right'),
    layerLocking: stringIDToTypeID("layerLocking"),
    protectAll: stringIDToTypeID('protectAll'),
    layerID: stringIDToTypeID("layerID"),
    group: stringIDToTypeID("group"),
    layerSection: stringIDToTypeID("layerSection"),
    textKey: stringIDToTypeID("textKey"),
    idNull: charIDToTypeID("null"),
    idDelete: charIDToTypeID("Dlt "),
    document: charIDToTypeID("Dcmn"),
    ordinal: charIDToTypeID("Ordn"),
    target: charIDToTypeID("Trgt"),
    hide: charIDToTypeID("Hd  "),
    application: charIDToTypeID("capp"),
    set: charIDToTypeID("setd"),
    to: charIDToTypeID("T   "),
    playbackOptions: stringIDToTypeID("playbackOptions"),
    hasBackgroundLayer: stringIDToTypeID("hasBackgroundLayer"),
    performance: stringIDToTypeID("performance"),
    layerSectionEnd: stringIDToTypeID("layerSectionEnd"),
    layerSectionStart: stringIDToTypeID("layerSectionStart"),
    layerSectionContent: stringIDToTypeID("layerSectionContent"),
    numberOfLayers: stringIDToTypeID("numberOfLayers"),
    accelerated: stringIDToTypeID("accelerated")
};
main();
// Record the script in the Actions palette when recording an action
try {
    var playbackDescription = new ActionDescriptor();
    var playbackReference = new ActionReference();
    playbackReference.putEnumerated(TID.document, TID.ordinal, TID.target);
    playbackDescription.putReference(TID.idNull, playbackReference);
    app.playbackDisplayDialogs = DialogModes.NO;
    app.playbackParameters = playbackDescription;
} catch (e) { /* do nothing */ }
isCancelled ? 'cancel' : undefined; // quit, returning 'cancel' (don't localize) makes the actions palette not record our script
/////////////////////////
// FUNCTIONS
/////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Function: main
// Usage: container function to hold all the working code that generates history states
// Input: <none> Must have an open document
// Return: <none>
///////////////////////////////////////////////////////////////////////////////
function main() {
    // there must be document
    // Document must have at least one layer so we don't need rum script if there is only one layer
    if (app.documents.length > 0) {
        numberOfLayers = getNumberOfLayers();
        backgroundCounter = getBackgroundLayerCounter();
        if(numberOfLayers + backgroundCounter > 1){
            try {
                doc = app.activeDocument;
                doc.suspendHistory(strDeleteAllEmptyLayersHistoryStepName, "runTask()");
            } catch (e) {
                isCancelled = true;
            }
        }
    }
}
function runTask() {
    acceleratePlayback();
    var deleteLayersList = [];
    var hideLayersList = [];
    var layers = new Array(numberOfLayers); // we know array lenght so we can reserve fixed space in memory
    var maxNestedLevels = 0;
    // We want to avoid DOM code here because it can be slow if document has a lot layers and nested layerSets. So we will use Action Manager code.
    for (var layerIndex = numberOfLayers, stepsInside = 0; layerIndex > 0; layerIndex--) { // stepsInside = how deep I am in folder structure
        var locked = getIsLocked(layerIndex);
        var layerType = getLayerType(layerIndex);
        var shouldRemove = false;
        var nestedLevels;
        // NOT layerSet
        if (layerType === 'layer') {
            nestedLevels = stepsInside + 1;
            shouldRemove = ((hasZeroDimensions(layerIndex) || getIsEmptyTextLayer(layerIndex)) && !locked);
        }
        // layerSet end - closing (invisible) layer
        else if (layerType === 'endOfLayerSet') {
            nestedLevels = stepsInside;
            stepsInside--;
        }
        // layerSet start - opening layer
        else if (layerType === 'startOfLayerSet') {
            stepsInside++;
            nestedLevels = stepsInside;
            shouldRemove = true; // we will check it later properly
        }
        if (nestedLevels > maxNestedLevels) {
            maxNestedLevels = nestedLevels;
        }
        var layerInfo = {
            nestedLevels: nestedLevels,
            layerType: layerType,
            itemIndex: layerIndex,
            itemID: getLayerId(layerIndex),
            remove: shouldRemove,
            locked: locked,
            isClipped: getIsClipped(layerIndex)
        };
        layers[numberOfLayers - layerIndex] = layerInfo;
    }
    resolveLayerSetsWithContent();
    resolveChildsOfLockedLayerSets();
    resolveClippingMasks();
    addLayersToDeleteLayersList();
    if (deleteLayersList && deleteLayersList.length) { // if there is something to delete
        // if layer which we want delete has clipping mask, then we hide clipping mask layers
        if (hideLayersList && hideLayersList.length) {runHideLayers(hideLayersList);}
        runDeleteLayers(deleteLayersList);
    }
    
    // we traverse layers from most nested levels to more shallow levels
    // so we can tell parent layerSets that we don't want remove them because they will have content
    // we traverse all layers in document for each level. Maximum nested levels is 10 and maximum layers is 8000. This means 80 000 cycles in extreme case.
    function resolveLayerSetsWithContent(){
        for (var j = 1; j < maxNestedLevels; maxNestedLevels--) {
            for (var i = 0; i < numberOfLayers; i++) {
                var layer = layers[i];
                
                if (layer.nestedLevels === maxNestedLevels) {
                    if(
                        // don't remove parent layerSet if we want keep its content
                        (layer.layerType === 'layer' && !layer.remove) ||
                        // or don't remove locked parent group or don't remove parent folder if children folder shouldn't be deleted
                        ((layer.locked || !layer.remove) && layer.layerType === 'startOfLayerSet')
                    ){ 
                        var parrentLayerSet = layers[getParentLayerSet(i)];
                        parrentLayerSet.remove = false;
                    }
                }
            }
        }
    }
    
    // excludes all childs from delete list if parent layerSet is locked
    function resolveChildsOfLockedLayerSets() {
        for (var j = 0; j < numberOfLayers; j++) { 
            //var layer = layers[j];
            if (layers[j].locked && layers[j].layerType === 'startOfLayerSet') {
                var initialNestedLevel = layers[j].nestedLevels;
                j++;
                while (initialNestedLevel < layers[j].nestedLevels) {
                    layers[j].remove = false;
                    j++;
                }
            }
        }
    }
    // don't mess up clipping mask layers - if we remove layer, then clipped layers might become visible so we hide them
    function resolveClippingMasks(){
        for (var j = 0; j < numberOfLayers; j++) { 
            var layer = layers[j];
            if (layer.isClipped) {
                var tempList = []; // here we store all clipped layers indexes
                while (layer.isClipped) { // this goes from top layer to bottom layer in layers panel and layers clipped to nothing are impossible
                    tempList.push(layer.itemIndex);
                    layer = layers[++j];
                }
                if (layer.remove && !layer.locked) {
                    hideLayersList = hideLayersList.concat(tempList);
                }
            }
        }       
    }
    // just move layer from one list to another list
    function addLayersToDeleteLayersList() {
        for (var j = 0; j < numberOfLayers; j++) {
            var layer = layers[j];
            if (layer.remove && !layer.locked) {
                deleteLayersList.push(layer.itemID);
            }
        }
    }
    // this will find parental layerSet for current layer or layerSet
    function getParentLayerSet(p) { 
        for (var i = p - 1; i > 0; i--) {
            if (layers[i].nestedLevels === layers[p].nestedLevels - 1) {
                return i;
            }
        }
        return 0;
    }
}
//////////////////////////////
// READ DOCUMENT PROPERTIES
//////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Function: hasZeroDimensions
// Usage: we read layer dimensions and if are zero then layer has zero visible pixels (they might be hidden with vector or bitmap mask)
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function hasZeroDimensions(index) {
    var desc = getLayerPropertyDescriptor(index, TID.bounds);
    var bounds = desc.getObjectValue(TID.bounds);
    var left = bounds.getDouble(TID.left);
    var right = bounds.getDouble(TID.right);
    var top = bounds.getDouble(TID.top);
    var bottom = bounds.getDouble(TID.bottom);
    var result = (left === right) && (top === bottom);
    return result;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsClipped
// Usage: we don't want remove locked layers. There are multiple kinds of locks. We need only "protectAll"
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function getIsLocked(index) {
    var desc = getLayerPropertyDescriptor(index, TID.layerLocking);
    var descLocking = desc.getObjectValue(TID.layerLocking);
    var locked = descLocking.getBoolean(TID.protectAll);
    return locked;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsClipped
// Usage: returns ID of layer so we can target layer no matter of layer position in layers panel
// Input: index of desired layer
// Return: Integer
///////////////////////////////////////////////////////////////////////////////
function getLayerId(index) {
    var desc = getLayerPropertyDescriptor(index, TID.layerID);
    var id = desc.getInteger(TID.layerID);
    return id;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsClipped
// Usage: returns true if layer is clipping mask
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function getIsClipped(index) {
    var desc = getLayerPropertyDescriptor(index, TID.group);
    var group = desc.getBoolean(TID.group);
    return group;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getLayerType
// Usage: returns type of layer
// Input: index of desired layer
// Return: String
///////////////////////////////////////////////////////////////////////////////
function getLayerType(index) {
    var desc = getLayerPropertyDescriptor(index, TID.layerSection);
    var type = desc.getEnumerationValue(TID.layerSection);
    switch (type) {
        case TID.layerSectionEnd:
            return 'endOfLayerSet';
        case TID.layerSectionStart:
            return 'startOfLayerSet';
        case TID.layerSectionContent:
            return 'layer';
        default:
            return undefined;
    }
}
///////////////////////////////////////////////////////////////////////////////
// Function: getNumberOfLayers
// Usage: returns number of all layers in document
// Input: <none> Must have an open document
// Return: Integer
///////////////////////////////////////////////////////////////////////////////
function getNumberOfLayers() {
    var desc = getDocumentPropertyDescriptor(TID.numberOfLayers);
    var numberOfLayers = desc.getInteger(TID.numberOfLayers);
    return numberOfLayers;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getIsEmptyTextLayer
// Usage: returns number of all layers in document
// Input: index of desired layer
// Return: Boolean
///////////////////////////////////////////////////////////////////////////////
function getIsEmptyTextLayer(index) {
    var textKey = getLayerPropertyDescriptor(index, TID.textKey);
    
    var isTextLayer = textKey.hasKey(TID.textKey);
    if (!isTextLayer) {
        return false;
    }
    var contentString = textKey.getObjectValue(TID.textKey).getString(TID.textKey);
    var result = (contentString === "");
    return result;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getBackgroundLayerCounter
// Usage: returns if document has background layer
// Input: <none> Must have an open document
// Return: 1 or 0
///////////////////////////////////////////////////////////////////////////////
function getBackgroundLayerCounter() {
    var desc = getDocumentPropertyDescriptor(TID.hasBackgroundLayer);
    var result = Number(desc.getBoolean(TID.hasBackgroundLayer));
    return result;
}
/////////////
// UTILITY
/////////////
///////////////////////////////////////////////////////////////////////////////
// Function: getLayerPropertyDescriptor
// Usage: shortcut helper function which return info about layer property
// Input: Index of desired layer, typeID of desired property
// Return: ActionDescriptor
///////////////////////////////////////////////////////////////////////////////
function getLayerPropertyDescriptor(index, property) {
    var ref = new ActionReference();
    ref.putProperty(TID.property, property);
    ref.putIndex(TID.layer, index);
    var desc = executeActionGet(ref);
    return desc;
}
///////////////////////////////////////////////////////////////////////////////
// Function: getDocumentPropertyDescriptor
// Usage: shortcut helper function which return info about current document property
// Input: TypeID of desired property
// Return: ActionDescriptor
///////////////////////////////////////////////////////////////////////////////
function getDocumentPropertyDescriptor(property) {
    var ref = new ActionReference();
    ref.putProperty(TID.property, property);
    ref.putEnumerated(TID.document, TID.ordinal, TID.target);
    var desc = executeActionGet(ref);
    return desc;
}
///////////////
// ACTIONS
///////////////
///////////////////////////////////////////////////////////////////////////////
// Function: runHideLayers
// Usage: sets visibility of multiple layers to hidden
// Input: Array of layerIndexes
// Return: undefined
///////////////////////////////////////////////////////////////////////////////
function runHideLayers(hideLayersList) {
    var desc = new ActionDescriptor();
    var list = new ActionList();
    for (var i = 0, len = hideLayersList.length; i < len; i++) {
        var ref = new ActionReference();
        ref.putIndex(TID.layer, hideLayersList[i]);
        list.putReference(ref);
    }
    desc.putList(TID.idNull, list);
    executeAction(TID.hide, desc, DialogModes.NO);
}
///////////////////////////////////////////////////////////////////////////////
// Function: runDeleteLayers
// Usage: deletes multiple layers one by one accoring layerID
// Input: Array of layerIDs
// Return: undefined
///////////////////////////////////////////////////////////////////////////////
function runDeleteLayers (list) {
    var desc = new ActionDescriptor();
    var layerRef = new ActionReference();
    for (var i = list.length - 1, len = i; i >= 0; i--) {
        layerRef.putIdentifier(TID.layer, list[i]);
    }
    desc.putReference(TID.idNull, layerRef);
    // "Try" because document must have at least one layer ...what if all layers would be empty?
    // And single layerSet always has 2 layers so it's a bit complicated
    try{
        executeAction(TID.idDelete, desc, DialogModes.NO);
    }catch(e){
        runDeleteLayersFallBack(list);
    }
}
function runDeleteLayersFallBack(list) {
    for (var i = list.length - 1, len = i; i >= 0; i--) {
        var desc = new ActionDescriptor();
        var layerRef = new ActionReference();
        layerRef.putIdentifier(TID.layer, list[i]);
        desc.putReference(TID.idNull, layerRef);
        // Try because document must have at least one layer ...what if all layers would be empty?
        // And single layerSet always has 2 layers so it's a bit complicated
        try{
            executeAction(TID.idDelete, desc, DialogModes.NO);
        }catch(e){
            /* do nothing */
        }
    }
}
///////////////////////////////////////////////////////////////////////////////
// Function: acceleratePlayback
// Usage: in action preferences can be set delay between each action so we make sure that there is no delay
// Input: <none>
// Return: undefined
///////////////////////////////////////////////////////////////////////////////
function acceleratePlayback() {
    var desc = new ActionDescriptor();
    var ref = new ActionReference();
    var desc2 = new ActionDescriptor();
    ref.putProperty(TID.property, TID.playbackOptions);
    ref.putEnumerated(TID.application, TID.ordinal, TID.target);
    desc.putReference(TID.idNull, ref);
    desc2.putEnumerated(TID.performance, TID.performance, TID.accelerated);
    desc.putObject(TID.to, TID.playbackOptions, desc2);
    executeAction(TID.set, desc, DialogModes.NO);
}
// End Delete All Empty Layers.jsx