UNPKG

n8n-nodes-image-editor-pro

Version:

Advanced image editing node for n8n using Sharp and Canvas

349 lines (348 loc) 14.3 kB
"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;