n8n-nodes-image-editor-pro
Version:
Advanced image editing node for n8n using Sharp and Canvas
349 lines (348 loc) • 14.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ImageEditorPro = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const ImageEditorPro_utils_1 = require("./ImageEditorPro.utils");
class ImageEditorPro {
constructor() {
this.description = {
displayName: 'ImageEditorPro',
name: 'imageEditorPro',
group: ['transform'],
version: 1,
description: 'Advanced image editing: collage, text overlay, watermark',
defaults: {
name: 'ImageEditorPro',
},
inputs: ['main'],
outputs: ['main'],
codex: {
categories: ['Image Processing'],
},
properties: [
{
displayName: 'Mode',
name: 'mode',
type: 'options',
options: [
{ name: 'Collage', value: 'collage' },
{ name: 'Add Text', value: 'addText' },
{ name: 'Add Watermark', value: 'addWatermark' },
],
default: 'collage',
},
{
displayName: 'Image URLs',
name: 'imageUrls',
type: 'string',
default: '',
placeholder: 'https://example.com/image1.png,https://example.com/image2.jpg',
},
// Collage options
{
displayName: 'Rows',
name: 'rows',
type: 'number',
default: 2,
displayOptions: { show: { mode: ['collage'] } },
},
{
displayName: 'Columns',
name: 'columns',
type: 'number',
default: 2,
displayOptions: { show: { mode: ['collage'] } },
},
{
displayName: 'Spacing',
name: 'spacing',
type: 'number',
default: 10,
displayOptions: { show: { mode: ['collage'] } },
},
{
displayName: 'Background Color',
name: 'backgroundColor',
type: 'color',
default: '#ffffff',
displayOptions: { show: { mode: ['collage'] } },
},
// Add Text options
{
displayName: 'Text',
name: 'text',
type: 'string',
default: 'Sample',
displayOptions: { show: { mode: ['addText'] } },
},
{
displayName: 'Font Size',
name: 'fontSize',
type: 'number',
default: 48,
displayOptions: { show: { mode: ['addText'] } },
},
{
displayName: 'Text Color',
name: 'color',
type: 'color',
default: '#000000',
displayOptions: { show: { mode: ['addText'] } },
},
{
displayName: 'Text Background Shape',
name: 'textBackgroundShape',
type: 'options',
options: [
{ name: 'None', value: 'none' },
{ name: 'Circle', value: 'circle' },
{ name: 'Rectangle', value: 'rectangle' },
],
default: 'none',
displayOptions: { show: { mode: ['addText'] } },
},
{
displayName: 'Text Background Color',
name: 'textBackgroundColor',
type: 'color',
default: '#ffffff',
displayOptions: { show: { mode: ['addText'], textBackgroundShape: ['circle', 'rectangle'] } },
},
{
displayName: 'Text Border Color',
name: 'textBorderColor',
type: 'color',
default: '#000000',
displayOptions: { show: { mode: ['addText'], textBackgroundShape: ['circle', 'rectangle'] } },
},
{
displayName: 'Shape Width',
name: 'shapeWidth',
type: 'number',
default: 100,
description: 'The width of the background shape',
displayOptions: {
show: {
mode: ['addText'],
textBackgroundShape: ['circle', 'rectangle', 'square'],
},
},
},
{
displayName: 'Shape Padding',
name: 'shapePadding',
type: 'number',
default: 20,
description: 'Padding (in pixels) between the shape border and the text',
displayOptions: {
show: {
mode: ['addText'],
textBackgroundShape: ['circle', 'rectangle', 'square'],
},
},
},
{
displayName: 'Shape Height',
name: 'shapeHeight',
type: 'number',
default: 100,
description: 'The height of the background shape',
displayOptions: {
show: {
mode: ['addText'],
textBackgroundShape: ['rectangle', 'square'],
},
},
},
{
displayName: 'Text Alignment in Shape',
name: 'textAlignInShape',
type: 'options',
default: 'center',
description: 'Position of the text within the background shape',
options: [
{ name: 'Top', value: 'top' },
{ name: 'Center', value: 'center' },
{ name: 'Bottom', value: 'bottom' },
{ name: 'Custom', value: 'custom' },
],
displayOptions: {
show: {
mode: ['addText'],
textBackgroundShape: ['circle', 'rectangle', 'square'],
},
},
},
{
displayName: 'Text X Offset (inside shape)',
name: 'textOffsetX',
type: 'number',
default: 0,
displayOptions: {
show: {
mode: ['addText'],
textBackgroundShape: ['circle', 'rectangle', 'square'],
textAlignInShape: ['custom'],
},
},
},
{
displayName: 'Text Y Offset (inside shape)',
name: 'textOffsetY',
type: 'number',
default: 0,
displayOptions: {
show: {
mode: ['addText'],
textBackgroundShape: ['circle', 'rectangle', 'square'],
textAlignInShape: ['custom'],
},
},
},
{
displayName: 'Position',
name: 'position',
type: 'options',
options: [
{ name: 'Top Left', value: 'top-left' },
{ name: 'Center', value: 'center' },
{ name: 'Bottom Right', value: 'bottom-right' },
{ name: 'Custom', value: 'custom' },
],
default: 'center',
displayOptions: { show: { mode: ['addText', 'addWatermark'] } },
},
{
displayName: 'Custom X',
name: 'customX',
type: 'number',
default: 50,
displayOptions: { show: { position: ['custom'] } },
},
{
displayName: 'Custom Y',
name: 'customY',
type: 'number',
default: 50,
displayOptions: { show: { position: ['custom'] } },
},
{
displayName: 'Opacity',
name: 'opacity',
type: 'number',
default: 1,
typeOptions: {
minValue: 0,
maxValue: 1,
},
displayOptions: { show: { mode: ['addText', 'addWatermark'] } },
},
// Watermark options
{
displayName: 'Watermark Text',
name: 'watermarkText',
type: 'string',
default: '© n8n',
displayOptions: { show: { mode: ['addWatermark'] } },
},
],
};
}
async execute() {
const mode = this.getNodeParameter('mode', 0);
// Get string of URLs (comma-separated)
const imageUrlsRaw = this.getNodeParameter('imageUrls', 0, '');
const imageUrlsStr = typeof imageUrlsRaw === 'string' ? imageUrlsRaw : '';
const urls = imageUrlsStr
.split(',')
.map((u) => u.trim())
.filter((u) => /^https?:\/\//.test(u));
// Try to get binary input if available
const inputItems = this.getInputData();
const binaryArray = inputItems
.map((item) => { var _a; return (_a = item.binary) === null || _a === void 0 ? void 0 : _a.data; })
.filter((b) => b !== undefined);
const binary = binaryArray.length === 1 ? binaryArray[0] : undefined;
// Throw error if neither URL nor binary was provided
if (urls.length === 0 && binaryArray.length === 0) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Must provide either image URLs or binary image input.');
}
// Normalize the input
const input = {
urls,
binary,
binaryArray: binaryArray.length > 1 ? binaryArray : undefined,
};
let options;
if (mode === 'collage') {
options = {
rows: this.getNodeParameter('rows', 0),
columns: this.getNodeParameter('columns', 0),
spacing: this.getNodeParameter('spacing', 0),
backgroundColor: this.getNodeParameter('backgroundColor', 0),
};
}
else if (mode === 'addText') {
const shape = this.getNodeParameter('textBackgroundShape', 0);
const shapeWidth = this.getNodeParameter('shapeWidth', 0);
const shapeHeight = ['rectangle', 'square'].includes(shape)
? this.getNodeParameter('shapeHeight', 0)
: undefined;
const shapePadding = this.getNodeParameter('shapePadding', 0);
const positionParam = this.getNodeParameter('position', 0);
const textAlignInShape = this.getNodeParameter('textAlignInShape', 0);
const textOffsetX = textAlignInShape === 'custom' ? this.getNodeParameter('textOffsetX', 0) : undefined;
const textOffsetY = textAlignInShape === 'custom' ? this.getNodeParameter('textOffsetY', 0) : undefined;
const position = positionParam === 'custom'
? {
x: this.getNodeParameter('customX', 0),
y: this.getNodeParameter('customY', 0),
}
: positionParam;
options = {
text: this.getNodeParameter('text', 0),
fontSize: this.getNodeParameter('fontSize', 0),
color: this.getNodeParameter('color', 0),
position,
opacity: this.getNodeParameter('opacity', 0),
backgroundShape: shape,
backgroundColor: this.getNodeParameter('textBackgroundColor', 0),
borderColor: this.getNodeParameter('textBorderColor', 0),
shapeWidth,
shapeHeight,
shapePadding,
textAlignInShape,
textOffsetX,
textOffsetY,
};
}
else {
// watermark mode
const positionParam = this.getNodeParameter('position', 0);
const position = positionParam === 'custom'
? {
x: this.getNodeParameter('customX', 0),
y: this.getNodeParameter('customY', 0),
}
: positionParam;
options = {
content: this.getNodeParameter('watermarkText', 0),
position,
opacity: this.getNodeParameter('opacity', 0),
};
}
// Run the main image editor logic
const buffer = await (0, ImageEditorPro_utils_1.imageEditor)({ mode, input, options });
return [
[
{
json: {},
binary: {
data: await this.helpers.prepareBinaryData(buffer, 'output.png', 'image/png'),
},
},
],
];
}
}
exports.ImageEditorPro = ImageEditorPro;