Skip to main content
Known Participant
July 20, 2022
Answered

Is it possible to convert RasterItem to placedItem?

  • July 20, 2022
  • 3 replies
  • 13254 views

Hi all , is it possible to convert RasterItem to placedItem in illustrator? Using script.

i have rasterise the placedItem manually but when I check it through the script It shows the typename as placedItem.

This topic has been closed for replies.
Correct answer m1b

Hi @Abpujar, I have good news I think. I've been working on a script that will "unembed" raster items. Maybe this is what you need? Currently the script will "unembed" all raster items in document. It will save them into a "Links" folder in the same folder as the .ai document as .psd files and link them (so they will be linked placed items). I couldn't find any other scripts that solved this problem. Let me know if it helps.

- Mark

 

 

/**
 * Unembed Raster Items
 * for Adobe Illustrator 2022
 * @author m1b
 * @discussion https://community.adobe.com/t5/illustrator-discussions/is-it-possible-to-convert-rasteritem-to-placeditem/m-p/13081172
 *
 * Known issues:
 * 1. raster items with effects applied
 * will be rasterized and the effect will
 * become part of the linked image;
 * 2. the resolution of the effect will match
 * the resolution of the embedded image, not
 * the document raster resolution setting,
 * and sometimes the effect scale will be wrong;
 * 3. some effects will cause the linked image
 * to be offset in position compared to the
 * embedded image.
 * 4. the script uses various sources to derive
 * the unembedded file's name, including the
 * file's XMP manifest, and this hasn't been
 * tested on a wide range of documents.
 */

(function () {

    // Will use the group name if it contains a file extension
    // (I recommend leaving this on, unless it causes a problem
    // in your case.)
    var useGroupNameAsFileName = true;

    // if testMode == true, don't close the temp document
    var testMode = false;

    if (app.documents.length > 0)
        unembedRasterImages(app.activeDocument, useGroupNameAsFileName);


    /**
     * Unembeds all rasterImages in document.
     * @author m1b
     * @version 2023-01-17
     * It will export embedding images as PSD
     * format into a 'Links' folder in the
     * same folder as the Illustrator document.
     * It will add suffix to exported images
     * to ensure it does NOT overwrite files
     * that already exist in the Links folder.
     * @param {Document} doc - an Illustrator document.
     * @param {Boolean} useGroupNameAsFileName - whether to Will use the group name if it contains a file extension (default: true).
     */
    function unembedRasterImages(doc, useGroupNameAsFileName) {

        var previousInteractionLevel = app.userInteractionLevel;
        app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;

        var myImages = doc.rasterItems,
            imageCount = myImages.length,
            removeMeUUIDs = [],
            exportErrors = [],
            unembeds = [],
            counter = 0;

        // set up export folder
        var exportFolder = Folder(doc.fullName.parent.fsName + '/Links/');
        if (!exportFolder.exists)
            exportFolder.create();

        // set up the images for export
        for (var i = imageCount - 1; i >= 0; i--) {

            var oldImage = myImages[i],
                colorSpace = oldImage.imageColorSpace,
                position = oldImage.position,

                // get current scale and rotation
                sr = getLinkScaleAndRotation(oldImage),
                scale = [sr[0], sr[1]],
                rotation = sr[2],

                width = oldImage.width,
                height = oldImage.height,

                fileExtensionRegex = /\.[^\.]+$/,
                imageTitle = oldImage.name.replace(fileExtensionRegex, '') || undefined;

            try {
                imageTitle = (decodeURIComponent(oldImage.file.name)).replace(fileExtensionRegex, '');
            } catch (error) { }

            if (
                useGroupNameAsFileName !== false
                && oldImage.parent.name.match(fileExtensionRegex)
            )
                imageTitle = oldImage.parent.name.replace(fileExtensionRegex, '');

            var workingTitle = imageTitle || 'image' + (i + 1);

            // script can't handle other colorSpaces
            if (
                colorSpace != ImageColorSpace.CMYK
                && colorSpace != ImageColorSpace.RGB
                && colorSpace != ImageColorSpace.GrayScale
            ) {
                exportErrors.push(workingTitle + ' has unsupported color space. (' + colorSpace + ')');
                continue;
            }

            // move to new document for exporting
            var temp = newDocument(workingTitle, colorSpace, 1000, 1000);

            // duplciate to the new document
            var workingImage = oldImage.duplicate(temp.layers[0], ElementPlacement.PLACEATBEGINNING);

            // set image to 100% scale 0° rotation and position
            var tm = app.getRotationMatrix(-rotation);
            tm = app.concatenateScaleMatrix(tm, 100 / scale[0] * 100, 100 / scale[1] * 100);
            workingImage.transform(tm, true, true, true, true, true);
            workingImage.position = [0, workingImage.height];
            temp.artboards[0].artboardRect = [0, workingImage.height, workingImage.width, 0];

            // export
            try {
                var workingFileName = new Date().getTime(),
                    path = exportFolder.fsName + '/' + workingFileName + '.psd',
                    // path = getFilePathWithOverwriteProtectionSuffix(exportFolder.fsName + '/' + workingFileName + '.psd'),
                    workingFile = exportAsPSD(temp, path, colorSpace, 72);
            } catch (error) {
                exportErrors.push('"' + imageTitle + '" failed to export. (' + error.message + ')');
            }


            if (!testMode)
                // close the temp document
                temp.close(SaveOptions.DONOTSAVECHANGES);

            if (
                workingFile == undefined
                || !workingFile.exists
            )
                continue;

            // see if the exported file has fileName in it's XMP manifest
            // var xmpFileName = getFilePathFromXMP(file, true);

            imageTitle = (
                imageTitle
                || getFilePathFromXMP(workingFile, true)
                || 'image' + (i + 1)
            );

            // rename the working file
            var newPath = exportFolder.fsName + '/' + imageTitle + '.psd';
            workingFile.copy(newPath);
            var file = File(newPath);
            workingFile.remove();

            // relink to the embedded image
            var newImage = oldImage.layer.placedItems.add();
            newImage.file = file;

            // scale and rotate and position
            // to match the original
            tm = app.getScaleMatrix(scale[0], scale[1]);
            tm = app.concatenateRotationMatrix(tm, rotation);
            newImage.transform(tm, true, true, true, true, true);
            newImage.move(oldImage, ElementPlacement.PLACEAFTER);
            newImage.position = position;

            if (
                round(oldImage.width, 2) != round(newImage.width, 2)
                || round(oldImage.height, 2) != round(newImage.height, 2)
            ) {
                // When size is different here, it probably means
                // there was an effect on the rasterItem eg.dropshadow.
                // Change `false` to `true` below if you want script
                // to ignore rasterItems that have this problem.
                if (false) {
                    exportErrors.push('Did not unembed "' + imageTitle + '" because it couldn\'t be sized correctly. Try removing special appearance and trying again.')
                    file.remove();
                    newImage.remove();
                    continue;
                }

                else
                    exportErrors.push('Warning: File "' + imageTitle + '" has altered dimensions, probably due to an effect or special appearance.');
            }

            // delete old image later
            removeMeUUIDs.push(oldImage.uuid);

            // for reporting
            counter++

        }

        // remove old embedded images
        for (var i = 0; i < removeMeUUIDs.length; i++) {
            var removeMe = getItemByUUID(myImages, removeMeUUIDs[i]);
            if (removeMe != undefined)
                removeMe.remove();
        }

        // clean up
        app.userInteractionLevel = previousInteractionLevel;
        app.redraw();

        // reporting
        var result = 'Unembedded ' + counter + ' of ' + imageCount + ' raster items.';
        if (exportErrors.length > 0)
            result += '\n' + exportErrors.join('\n');

        alert(result);

    };

    /**
     * Returns a page item with matching UUID.
     * @param {PageItems} items - Illustrator PageItems or Array of PageItems.
     * @param {Number} uuid - Illustrator PageItem uuid.
     * @returns {PageItem}
     */
    function getItemByUUID(items, uuid) {
        for (var i = 0; i < items.length; i++) {
            if (items[i].uuid === uuid)
                return items[i];
        }
    };

    /**
     * Exports a document as PSD.
     * @param {Document} doc - the document to export.
     * @param {String} path - name of file, without extension.
     * @param {ImageColorSpace} colorSpace - the colorSpace of the image.
     * @param {Number} resolution - export resolution
     * @returns {File} - the exported psd file.
     */
    function exportAsPSD(doc, path, colorSpace, resolution) {

        var file = File(path),
            options = new ExportOptionsPhotoshop();

        options.antiAliasing = false;
        options.artBoardClipping = true;
        options.imageColorSpace = colorSpace;
        options.editableText = false;
        options.flatten = true;
        options.maximumEditability = false;
        options.resolution = (resolution || 72);
        options.warnings = false;
        options.writeLayers = false;

        doc.exportFile(file, ExportType.PHOTOSHOP, options);

        return file;
    };

    /**
     * Return the scale, rotation and size
     * of a PlacedItem or RasterItem.
     * @author m1b
     * @version 2023-03-09
     * @param {PlacedItem|RasterItem} item - an Illustrator item.
     * @param {Boolean} round - whether to round numbers to nearest integer.
     * @returns {Array} [scaleX%, scaleY%, rotation°, width, height]
     */
    function getLinkScaleAndRotation(item, round) {

        if (item == undefined)
            return;

        var m = item.matrix,
            rotatedAmount,
            unrotatedMatrix,
            scaledAmount;

        var flipPlacedItem = (item.typename == 'PlacedItem') ? 1 : -1;

        try {
            rotatedAmount = item.tags.getByName('BBAccumRotation').value * 180 / Math.PI;
        } catch (error) {
            rotatedAmount = 0;
        }
        unrotatedMatrix = app.concatenateRotationMatrix(m, rotatedAmount * flipPlacedItem);

        if (
            unrotatedMatrix.mValueA == 0
            && unrotatedMatrix.mValueB !== 0
            && unrotatedMatrix.mValueC !== 0
            && unrotatedMatrix.mValueD == 0
        )
            scaledAmount = [unrotatedMatrix.mValueB * 100, unrotatedMatrix.mValueC * -100 * flipPlacedItem];
        else
            scaledAmount = [unrotatedMatrix.mValueA * 100, unrotatedMatrix.mValueD * -100 * flipPlacedItem];

        if (scaledAmount[0] == 0 || scaledAmount[1] == 0)
            return;

        if (round)
            return [round(scaledAmount[0]), round(scaledAmount[1]), round(rotatedAmount)];
        else
            return [scaledAmount[0], scaledAmount[1], rotatedAmount];

    };

    /**
     * Rounds `n` to `places` decimal places.
     * @param {Number} n - the number to round
     * @param {Number} places - number of decimal places, can be negative
     * @returns {Number}
     */
    function round(n, places) {
        var m = Math.pow(10, places != undefined ? places : 3);
        return Math.round(n * m) / m;
    };

    /**
     * Create a new basic document with some options.
     * @author m1b
     * @version 2022-07-21
     * @param {String} name - the title of the document.
     * @param {ImageColorSpace} colorSpace - ImageColorSpace.RGB or ImageColorSpace.CMYK.
     * @param {Number} width - width of the default artboard.
     * @param {Number} height - height of the default artboard.
     * @returns {Document}
     */
    function newDocument(name, colorSpace, width, height) {

        var myDocPreset = new DocumentPreset(),
            myDocPresetType;

        myDocPreset.title = name;
        myDocPreset.width = width || 1000;
        myDocPreset.height = height || 1000;

        if (
            colorSpace == ImageColorSpace.CMYK
            || colorSpace == ImageColorSpace.GrayScale
            || colorSpace == ImageColorSpace.DeviceN
        ) {
            myDocPresetType = DocumentPresetType.BasicCMYK;
            myDocPreset.colorMode = DocumentColorSpace.CMYK
        }

        else { // if (colorSpace == ImageColorSpace.RGB) {
            myDocPresetType = DocumentPresetType.BasicRGB;
            myDocPreset.colorMode = DocumentColorSpace.RGB
        }

        return app.documents.addDocument(myDocPresetType, myDocPreset);

    };

    /**
     * Returns a path that will not overwrite a file.
     * Adds an incremental suffix to avoid overwrites.
     * @author m1b
     * @version 2022-07-20
     * For example, if `myFile.txt` already exists it
     * will return `myFile(1).txt` and if that already
     * exists, it will return `myFile(2).txt` etc.
     * @param {String} path - the path to test.
     * @returns {String} a path that will not overwrite a file.
     */
    function getFilePathWithOverwriteProtectionSuffix(path) {

        var index = 1,
            parts = path.split(/(\.[^\.]+)$/);

        while (File(path).exists)
            path = parts[0] + '(' + (++index) + ')' + parts[1];

        return path;
    };


    /**
     * Returns the file path stored in XMP data
     * of a psd file, if available.
     * @param {File} file - a File.
     * @param {Boolean} fileNameOnly - whether to return just file names (default: false).
     * @returns {String} - file path or file name or undefined.
     */
    function getFilePathFromXMP(file, fileNameOnly) {

        if (ExternalObject.AdobeXMPScript == undefined)
            ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');

        var xmpFile = new XMPFile(file.fsName, XMPConst.UNKNOWN, XMPConst.OPEN_FOR_READ),
            xmp = xmpFile.getXMP(),
            xpath = 'xmpMM:Manifest[1]/stMfs:reference/stRef:filePath',
            value = xmp.getProperty(XMPConst.NS_XMP_MM, xpath);

        if (
            value == undefined
            || !value.hasOwnProperty('value')
        )
            return;

        value = value.value;

        if (fileNameOnly !== false) {

            var onlyFileName = value.match(/\/([^\/]+)\.[^\.\/]+/);

            if (
                onlyFileName != null
                && onlyFileName.length == 2
            )
                value = onlyFileName[1];

        }

        return value;

    };

})();

 

 

 

Edit 1: fixed un-localized string in newDocument function. Thanks @Kurt Gold!

Edit 2: fixed problem with rotated rasterItems and now shows warning in special cases. Again thanks @Kurt Gold!

Edit 3 (2022-12-03): added better file naming, as requested by @ignacio267368455fix. Good call!

Edit 4 (2022-12-03): @ignacio267368455fix found cases where the RasterItem's name was actually in the parent GroupItem. I've added an option "useGroupNameAsFileName" to handle this case.

Edit 5 (2023-01-15): added ability to use fileNames taken from the document's XMP manifest, to handle a case in @Filip.Czechowski's sample document. This feature could do with some more testing, but worked well for Filip's file.

Edit (2023-01-16): added a step to save the document, to ensure that the XMP manifest was updated because after reordering raster items in the document (without saving) the manifest would have the old ordering.

Edit (2023-01-17): my previous attempt to get meaningful file names using the document's XMP data didn't pan out in many cases, so I implemented an idea by @Filip.Czechowski to get the filename from the exported document's XMP data.

Edit (2023-03-09): Added a check for an edge case, found by @ajabon grinsmith, where the raster item had no BBAccumRotation tag and returned zero scale.

3 replies

Kurt Gold
Community Expert
Community Expert
July 21, 2022

Thanks, Mark. I think not using the localised presets/profiles is a good idea.

 

I hope you don't think that I'm going to be a nit-picky killjoy and don't mind that I did some more testing. As far as I can see, several other funny things may happen. For example:

 

- Scaled rotated images may change their original dimensions, positions, and sometimes their overall appearance.

 

- Images to which effects are applied (e.g. drop shadows, outer glow, feather or additional fills/strokes etc.) will be unembedded, but the results may be pretty "surprising" (e.g. "giant" sizes), the effects may get removed and sometimes even entire images disappear. Also, the original positions may shift considerably.

 

- If an embedded raster image contains at least one spot colour, unembedding will fail or produce undesired results.

 

You can download an Illustrator test file that contains some of the objects mentioned above.

 

Unembedder – Test file 1

 

Just open the document, run your script, wait until it's done and compare it to the initial file.

 

m1b
Community Expert
Community Expert
July 23, 2022

Hey @Kurt Gold, this is awesome analysis. Thanks very much! I've updated the script above including an important fix to do with rotated rasterItems (I just had the matrix operations backwards in one place). Unfortunately there isn't an easy fix for any of the other cases. I've left them in, with the option for the script to just ignore them if user prefers, but they will generate a warning message when the results are shown. See the screenshot below for notes on your sample file.

- Mark

Kurt Gold
Community Expert
Community Expert
July 24, 2022

Thank you, Mark.

 

Right now I'm not going to make things more complicated than necessary. Your script is very good, just as is. Of course, there may be some other special cases that could disturb it, but for no-nonsense plain embedded raster images it works properly.

 

No doubt, it exemplarily shows that it may be always a bit chancy to use scripts (or actions) if there are files with unknown or unexpected occurrences.

 

Kurt Gold
Community Expert
Community Expert
July 20, 2022

Very good approach, Mark. Thank you for sharing.

 

I did some quick tests and basically the script is running well.

 

But as far as I can see, currently there will be some localisation issues if you use it with non-English international Illustrator versions.

 

For example, in line 253:

 

myDocPreset = app.getPresetSettings('Print');

 

The preset 'Print' (or its name) does not exist in e.g. the German version of Illustrator. Therefore the script will be interrupted.

 

You would have to replace it with the localised name 'Druck' or various completely different names in other non-English Illustrator versions.

 

m1b
Community Expert
Community Expert
July 20, 2022

Thanks for testing it out, Kurt and great comment about language! Could I use the index of startup presets list? Could you please check these:

alert(app.startupPresetsList[0] + '\nDoes this mean "print"?');
alert(app.startupPresetsList[2] + '\nDoes this mean "web"?');

- Mark

Kurt Gold
Community Expert
Community Expert
July 20, 2022

Not as you may think (or hope), Mark.

 

Line 1 [0] alerts the profile "Grafik und Illustration" (in English: "Art & Illustration")

 

Line 2 [2] alerts the profile "Film und Video" (in English: "Film and Video")

 

I think using the index may get even more confusing if you have (a lot of) custom profiles in the dropdown menu.

 

m1b
Community Expert
m1bCommunity ExpertCorrect answer
Community Expert
July 20, 2022

Hi @Abpujar, I have good news I think. I've been working on a script that will "unembed" raster items. Maybe this is what you need? Currently the script will "unembed" all raster items in document. It will save them into a "Links" folder in the same folder as the .ai document as .psd files and link them (so they will be linked placed items). I couldn't find any other scripts that solved this problem. Let me know if it helps.

- Mark

 

 

/**
 * Unembed Raster Items
 * for Adobe Illustrator 2022
 * @author m1b
 * @discussion https://community.adobe.com/t5/illustrator-discussions/is-it-possible-to-convert-rasteritem-to-placeditem/m-p/13081172
 *
 * Known issues:
 * 1. raster items with effects applied
 * will be rasterized and the effect will
 * become part of the linked image;
 * 2. the resolution of the effect will match
 * the resolution of the embedded image, not
 * the document raster resolution setting,
 * and sometimes the effect scale will be wrong;
 * 3. some effects will cause the linked image
 * to be offset in position compared to the
 * embedded image.
 * 4. the script uses various sources to derive
 * the unembedded file's name, including the
 * file's XMP manifest, and this hasn't been
 * tested on a wide range of documents.
 */

(function () {

    // Will use the group name if it contains a file extension
    // (I recommend leaving this on, unless it causes a problem
    // in your case.)
    var useGroupNameAsFileName = true;

    // if testMode == true, don't close the temp document
    var testMode = false;

    if (app.documents.length > 0)
        unembedRasterImages(app.activeDocument, useGroupNameAsFileName);


    /**
     * Unembeds all rasterImages in document.
     * @author m1b
     * @version 2023-01-17
     * It will export embedding images as PSD
     * format into a 'Links' folder in the
     * same folder as the Illustrator document.
     * It will add suffix to exported images
     * to ensure it does NOT overwrite files
     * that already exist in the Links folder.
     * @param {Document} doc - an Illustrator document.
     * @param {Boolean} useGroupNameAsFileName - whether to Will use the group name if it contains a file extension (default: true).
     */
    function unembedRasterImages(doc, useGroupNameAsFileName) {

        var previousInteractionLevel = app.userInteractionLevel;
        app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;

        var myImages = doc.rasterItems,
            imageCount = myImages.length,
            removeMeUUIDs = [],
            exportErrors = [],
            unembeds = [],
            counter = 0;

        // set up export folder
        var exportFolder = Folder(doc.fullName.parent.fsName + '/Links/');
        if (!exportFolder.exists)
            exportFolder.create();

        // set up the images for export
        for (var i = imageCount - 1; i >= 0; i--) {

            var oldImage = myImages[i],
                colorSpace = oldImage.imageColorSpace,
                position = oldImage.position,

                // get current scale and rotation
                sr = getLinkScaleAndRotation(oldImage),
                scale = [sr[0], sr[1]],
                rotation = sr[2],

                width = oldImage.width,
                height = oldImage.height,

                fileExtensionRegex = /\.[^\.]+$/,
                imageTitle = oldImage.name.replace(fileExtensionRegex, '') || undefined;

            try {
                imageTitle = (decodeURIComponent(oldImage.file.name)).replace(fileExtensionRegex, '');
            } catch (error) { }

            if (
                useGroupNameAsFileName !== false
                && oldImage.parent.name.match(fileExtensionRegex)
            )
                imageTitle = oldImage.parent.name.replace(fileExtensionRegex, '');

            var workingTitle = imageTitle || 'image' + (i + 1);

            // script can't handle other colorSpaces
            if (
                colorSpace != ImageColorSpace.CMYK
                && colorSpace != ImageColorSpace.RGB
                && colorSpace != ImageColorSpace.GrayScale
            ) {
                exportErrors.push(workingTitle + ' has unsupported color space. (' + colorSpace + ')');
                continue;
            }

            // move to new document for exporting
            var temp = newDocument(workingTitle, colorSpace, 1000, 1000);

            // duplciate to the new document
            var workingImage = oldImage.duplicate(temp.layers[0], ElementPlacement.PLACEATBEGINNING);

            // set image to 100% scale 0° rotation and position
            var tm = app.getRotationMatrix(-rotation);
            tm = app.concatenateScaleMatrix(tm, 100 / scale[0] * 100, 100 / scale[1] * 100);
            workingImage.transform(tm, true, true, true, true, true);
            workingImage.position = [0, workingImage.height];
            temp.artboards[0].artboardRect = [0, workingImage.height, workingImage.width, 0];

            // export
            try {
                var workingFileName = new Date().getTime(),
                    path = exportFolder.fsName + '/' + workingFileName + '.psd',
                    // path = getFilePathWithOverwriteProtectionSuffix(exportFolder.fsName + '/' + workingFileName + '.psd'),
                    workingFile = exportAsPSD(temp, path, colorSpace, 72);
            } catch (error) {
                exportErrors.push('"' + imageTitle + '" failed to export. (' + error.message + ')');
            }


            if (!testMode)
                // close the temp document
                temp.close(SaveOptions.DONOTSAVECHANGES);

            if (
                workingFile == undefined
                || !workingFile.exists
            )
                continue;

            // see if the exported file has fileName in it's XMP manifest
            // var xmpFileName = getFilePathFromXMP(file, true);

            imageTitle = (
                imageTitle
                || getFilePathFromXMP(workingFile, true)
                || 'image' + (i + 1)
            );

            // rename the working file
            var newPath = exportFolder.fsName + '/' + imageTitle + '.psd';
            workingFile.copy(newPath);
            var file = File(newPath);
            workingFile.remove();

            // relink to the embedded image
            var newImage = oldImage.layer.placedItems.add();
            newImage.file = file;

            // scale and rotate and position
            // to match the original
            tm = app.getScaleMatrix(scale[0], scale[1]);
            tm = app.concatenateRotationMatrix(tm, rotation);
            newImage.transform(tm, true, true, true, true, true);
            newImage.move(oldImage, ElementPlacement.PLACEAFTER);
            newImage.position = position;

            if (
                round(oldImage.width, 2) != round(newImage.width, 2)
                || round(oldImage.height, 2) != round(newImage.height, 2)
            ) {
                // When size is different here, it probably means
                // there was an effect on the rasterItem eg.dropshadow.
                // Change `false` to `true` below if you want script
                // to ignore rasterItems that have this problem.
                if (false) {
                    exportErrors.push('Did not unembed "' + imageTitle + '" because it couldn\'t be sized correctly. Try removing special appearance and trying again.')
                    file.remove();
                    newImage.remove();
                    continue;
                }

                else
                    exportErrors.push('Warning: File "' + imageTitle + '" has altered dimensions, probably due to an effect or special appearance.');
            }

            // delete old image later
            removeMeUUIDs.push(oldImage.uuid);

            // for reporting
            counter++

        }

        // remove old embedded images
        for (var i = 0; i < removeMeUUIDs.length; i++) {
            var removeMe = getItemByUUID(myImages, removeMeUUIDs[i]);
            if (removeMe != undefined)
                removeMe.remove();
        }

        // clean up
        app.userInteractionLevel = previousInteractionLevel;
        app.redraw();

        // reporting
        var result = 'Unembedded ' + counter + ' of ' + imageCount + ' raster items.';
        if (exportErrors.length > 0)
            result += '\n' + exportErrors.join('\n');

        alert(result);

    };

    /**
     * Returns a page item with matching UUID.
     * @param {PageItems} items - Illustrator PageItems or Array of PageItems.
     * @param {Number} uuid - Illustrator PageItem uuid.
     * @returns {PageItem}
     */
    function getItemByUUID(items, uuid) {
        for (var i = 0; i < items.length; i++) {
            if (items[i].uuid === uuid)
                return items[i];
        }
    };

    /**
     * Exports a document as PSD.
     * @param {Document} doc - the document to export.
     * @param {String} path - name of file, without extension.
     * @param {ImageColorSpace} colorSpace - the colorSpace of the image.
     * @param {Number} resolution - export resolution
     * @returns {File} - the exported psd file.
     */
    function exportAsPSD(doc, path, colorSpace, resolution) {

        var file = File(path),
            options = new ExportOptionsPhotoshop();

        options.antiAliasing = false;
        options.artBoardClipping = true;
        options.imageColorSpace = colorSpace;
        options.editableText = false;
        options.flatten = true;
        options.maximumEditability = false;
        options.resolution = (resolution || 72);
        options.warnings = false;
        options.writeLayers = false;

        doc.exportFile(file, ExportType.PHOTOSHOP, options);

        return file;
    };

    /**
     * Return the scale, rotation and size
     * of a PlacedItem or RasterItem.
     * @author m1b
     * @version 2023-03-09
     * @param {PlacedItem|RasterItem} item - an Illustrator item.
     * @param {Boolean} round - whether to round numbers to nearest integer.
     * @returns {Array} [scaleX%, scaleY%, rotation°, width, height]
     */
    function getLinkScaleAndRotation(item, round) {

        if (item == undefined)
            return;

        var m = item.matrix,
            rotatedAmount,
            unrotatedMatrix,
            scaledAmount;

        var flipPlacedItem = (item.typename == 'PlacedItem') ? 1 : -1;

        try {
            rotatedAmount = item.tags.getByName('BBAccumRotation').value * 180 / Math.PI;
        } catch (error) {
            rotatedAmount = 0;
        }
        unrotatedMatrix = app.concatenateRotationMatrix(m, rotatedAmount * flipPlacedItem);

        if (
            unrotatedMatrix.mValueA == 0
            && unrotatedMatrix.mValueB !== 0
            && unrotatedMatrix.mValueC !== 0
            && unrotatedMatrix.mValueD == 0
        )
            scaledAmount = [unrotatedMatrix.mValueB * 100, unrotatedMatrix.mValueC * -100 * flipPlacedItem];
        else
            scaledAmount = [unrotatedMatrix.mValueA * 100, unrotatedMatrix.mValueD * -100 * flipPlacedItem];

        if (scaledAmount[0] == 0 || scaledAmount[1] == 0)
            return;

        if (round)
            return [round(scaledAmount[0]), round(scaledAmount[1]), round(rotatedAmount)];
        else
            return [scaledAmount[0], scaledAmount[1], rotatedAmount];

    };

    /**
     * Rounds `n` to `places` decimal places.
     * @param {Number} n - the number to round
     * @param {Number} places - number of decimal places, can be negative
     * @returns {Number}
     */
    function round(n, places) {
        var m = Math.pow(10, places != undefined ? places : 3);
        return Math.round(n * m) / m;
    };

    /**
     * Create a new basic document with some options.
     * @author m1b
     * @version 2022-07-21
     * @param {String} name - the title of the document.
     * @param {ImageColorSpace} colorSpace - ImageColorSpace.RGB or ImageColorSpace.CMYK.
     * @param {Number} width - width of the default artboard.
     * @param {Number} height - height of the default artboard.
     * @returns {Document}
     */
    function newDocument(name, colorSpace, width, height) {

        var myDocPreset = new DocumentPreset(),
            myDocPresetType;

        myDocPreset.title = name;
        myDocPreset.width = width || 1000;
        myDocPreset.height = height || 1000;

        if (
            colorSpace == ImageColorSpace.CMYK
            || colorSpace == ImageColorSpace.GrayScale
            || colorSpace == ImageColorSpace.DeviceN
        ) {
            myDocPresetType = DocumentPresetType.BasicCMYK;
            myDocPreset.colorMode = DocumentColorSpace.CMYK
        }

        else { // if (colorSpace == ImageColorSpace.RGB) {
            myDocPresetType = DocumentPresetType.BasicRGB;
            myDocPreset.colorMode = DocumentColorSpace.RGB
        }

        return app.documents.addDocument(myDocPresetType, myDocPreset);

    };

    /**
     * Returns a path that will not overwrite a file.
     * Adds an incremental suffix to avoid overwrites.
     * @author m1b
     * @version 2022-07-20
     * For example, if `myFile.txt` already exists it
     * will return `myFile(1).txt` and if that already
     * exists, it will return `myFile(2).txt` etc.
     * @param {String} path - the path to test.
     * @returns {String} a path that will not overwrite a file.
     */
    function getFilePathWithOverwriteProtectionSuffix(path) {

        var index = 1,
            parts = path.split(/(\.[^\.]+)$/);

        while (File(path).exists)
            path = parts[0] + '(' + (++index) + ')' + parts[1];

        return path;
    };


    /**
     * Returns the file path stored in XMP data
     * of a psd file, if available.
     * @param {File} file - a File.
     * @param {Boolean} fileNameOnly - whether to return just file names (default: false).
     * @returns {String} - file path or file name or undefined.
     */
    function getFilePathFromXMP(file, fileNameOnly) {

        if (ExternalObject.AdobeXMPScript == undefined)
            ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');

        var xmpFile = new XMPFile(file.fsName, XMPConst.UNKNOWN, XMPConst.OPEN_FOR_READ),
            xmp = xmpFile.getXMP(),
            xpath = 'xmpMM:Manifest[1]/stMfs:reference/stRef:filePath',
            value = xmp.getProperty(XMPConst.NS_XMP_MM, xpath);

        if (
            value == undefined
            || !value.hasOwnProperty('value')
        )
            return;

        value = value.value;

        if (fileNameOnly !== false) {

            var onlyFileName = value.match(/\/([^\/]+)\.[^\.\/]+/);

            if (
                onlyFileName != null
                && onlyFileName.length == 2
            )
                value = onlyFileName[1];

        }

        return value;

    };

})();

 

 

 

Edit 1: fixed un-localized string in newDocument function. Thanks @Kurt Gold!

Edit 2: fixed problem with rotated rasterItems and now shows warning in special cases. Again thanks @Kurt Gold!

Edit 3 (2022-12-03): added better file naming, as requested by @ignacio267368455fix. Good call!

Edit 4 (2022-12-03): @ignacio267368455fix found cases where the RasterItem's name was actually in the parent GroupItem. I've added an option "useGroupNameAsFileName" to handle this case.

Edit 5 (2023-01-15): added ability to use fileNames taken from the document's XMP manifest, to handle a case in @Filip.Czechowski's sample document. This feature could do with some more testing, but worked well for Filip's file.

Edit (2023-01-16): added a step to save the document, to ensure that the XMP manifest was updated because after reordering raster items in the document (without saving) the manifest would have the old ordering.

Edit (2023-01-17): my previous attempt to get meaningful file names using the document's XMP data didn't pan out in many cases, so I implemented an idea by @Filip.Czechowski to get the filename from the exported document's XMP data.

Edit (2023-03-09): Added a check for an edge case, found by @ajabon grinsmith, where the raster item had no BBAccumRotation tag and returned zero scale.

Participating Frequently
December 2, 2022

this is awesome, works 100%

just need something to work perfect for me...any idea how to get the name of the embed image?

m1b
Community Expert
Community Expert
December 2, 2022

@ignacio267368455fix, if you run the above (updated) script and your file says "image1.psd" instead of the expected name, then it is almost certainly because the name is not in the illustrator file. I try to get the name from the raster item's name property, then from the file, but some embedded images don't have either of those. If you would like to post a sample document I can have a close look at it and confirm if a name exists. Or you could try a test: in the Links panel, select the image and go to the panel menu and choose "Unembed..." — when you do this, does it give the name you expected?

- Mark