Hi everyone,
I’m trying to get a base64 png of a pixel layer. Sadly, I get a blank image and couldn’t find online how to fix it. Here’s my current code
async function exportLayerToBase64PNG(layer) {
// 1) Get the layer bounds
const { bounds } = layer;
const layerWidth = bounds.right - bounds.left;
const layerHeight = bounds.bottom - bounds.top;
// 2) Create a temp document exactly that size
const tempDoc = await app.documents.add({
width: layerWidth,
height: layerHeight,
resolution: 72,
mode: "RGBColor",
name: "__tempExportDoc__"
});
// 3) Duplicate the layer into that temp doc
await action.batchPlay([{
_obj: "duplicate",
_target: [{ _ref: "layer", _id: layer.id }],
to: [{ _ref: "document", _id: tempDoc.id }]
}], { synchronousExecution: true });
// 4) Now move that new layer so its original top-left aligns with 0,0
// We know we pasted at absolute coords (bounds.left,bounds.top),
// so we need to shift by (-bounds.left, -bounds.top).
await action.batchPlay([{
_obj: "move",
_target: [{ _ref: "layer", _id: tempDoc.activeLayers[0].id }],
horizontal: { _unit: "pixelsUnit", _value: -bounds.left },
vertical: { _unit: "pixelsUnit", _value: -bounds.top }
}], { synchronousExecution: true });
// 5) Flatten so just that pixel data remains
await action.batchPlay([{ _obj: "flattenImage" }], { synchronousExecution: true });
// 6) Export to temp PNG
const tmpFolder = await localFileSystem.getTemporaryFolder();
const tmpFile = await tmpFolder.createFile("layerExport.png", { overwrite: true });
await tempDoc.saveAs.png(tmpFile, { transparency: true, embedColorProfile: true });
// 7) Read & base64-encode
const bytes = await tmpFile.read({ format: formats.binary });
let binary = "", b8 = new Uint8Array(bytes);
for (let i = 0; i < b8.byteLength; i++) binary += String.fromCharCode(b8[i]);
const b64 = btoa(binary);
// 8) Close and return a data-URI
await tempDoc.closeWithoutSaving();
return `data:image/png;base64,${b64}`;
}
Anybody knows how to do this? Thank you!
I made a function which uses the Imaging API to get the layer pixels of the selected layer and convert them into base64, it doesn’t matter the PSD size though it handles it well. I can share it with you:
// Remember to run this function withi an async function environment.
const base64Data = await convertPixelsToBase64Data();
// This should log something like this: x8cIh4dIx8fJB4eJBwbHx0XHBsWIB8aIB4cHB0XHRw... (6.2MB)
console.log(base64Data);
// The bigger the image of course the bigger the size of the bas64 data and the longer to process the function.
// MAIN FUNCTION
// Returns the base64 data from the selected PSD layer by using Adobe's Imaging API to extract the pixels bytes and converting into bas64.
async function convertPixelsToBase64Data() {
// Load Photoshop module and its needed functions.
const ps = require('photoshop');
const batchPlay = ps.app.batchPlay;
const executeAsModal = ps.core.executeAsModal;
const imaging = ps.imaging;
const app = ps.app;
// Initialize the variable base64Data to store our base64 data.
let base64Data = "";
// This needs to be executed inside a modal scope in order to function, we use the executeAsModal core function from the Photoshop module.
await executeAsModal(async () => {
try {
let binaryData = "";
// Amount of chunks that will be splited the bytes to avoid Maximum call stack size within the ctoa function.
const CHUNK_SIZE = 10000;
// Get user PS Document bitDepth to revert the bitDepth change in case it is needed.
const userDocBitDepth = parseInt(app.activeDocument.bitsPerChannel.replace("bitDepth", ""));
// Change the document to 8 bit to make it work.
// Otherwise with 16 and 32 bit images binary data won't be accepted within the btoa function. Only values from 0-255 are accepted.
if (userDocBitDepth != 8) await changeBitDepth(8);
// Get pixels from the selected layer.
const bytes = await getLayerPixels(app.activeDocument.activeLayers[0]); // Pixel bytes
// Split the bytes into chunks to avoid Maximum call stack size, this is crucial for things to work.
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
binaryData += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK_SIZE));
}
// Store the base64 data in the base64Data variable.
base64Data = btoa(binaryData);
} catch (e) {
console.error(e);
}
});
// Helper function to change the bitDepth of the PS document with the given bit depth number (8, 16 or 32).
async function changeBitDepth(bits) {
await batchPlay([{
"_obj": "convertMode",
"depth": bits,
"merge": false,
"rasterize": false
}], {});
}
// Use the Imaging API to get the pixels from the selected layer and return a
async function getLayerPixels(layer) {
const { bounds } = layer;
const layerWidth = bounds.right - bounds.left;
const layerHeight = bounds.bottom - bounds.top;
const pixelsOpt = {
layerID: layer.id,
sourceBounds: {
left: bounds.left,
top: bounds.top,
right: layerWidth,
bottom: layerHeight,
},
applyAlpha: true
}
const imageObj = await imaging.getPixels(pixelsOpt);
const pixels = await imageObj.imageData.getData();
// Clean memory usage faster than waiting for Javascript garbae collector to run.
// This is the correct memory management for this process
imageObj.imageData.dispose();
// const bitDepth = parseInt(app.activeDocument.bitsPerChannel.replace("bitDepth", ""));
// Return the imageData buffer in bytes, this handles automatically the bitDepth of the PS document.
return pixels;
}
return base64Data;
}
This does it using the pixel bounds though.
Hope this is what you were looking for!
2 Likes
Thank you! Yes this is what I was looking for 
1 Like