diff --git a/packages/nodes-base/nodes/EditImage.node.ts b/packages/nodes-base/nodes/EditImage.node.ts index 16c718e274..94f47d9c8f 100644 --- a/packages/nodes-base/nodes/EditImage.node.ts +++ b/packages/nodes-base/nodes/EditImage.node.ts @@ -4,17 +4,23 @@ import { } from 'n8n-core'; import { IDataObject, + ILoadOptionsFunctions, INodeExecutionData, + INodePropertyOptions, INodeType, INodeTypeDescription, } from 'n8n-workflow'; import * as gm from 'gm'; import { file } from 'tmp-promise'; +import { + parse as pathParse, +} from 'path'; import { writeFile as fsWriteFile, } from 'fs'; import { promisify } from 'util'; const fsWriteFileAsync = promisify(fsWriteFile); +import * as getSystemFonts from 'get-system-fonts'; export class EditImage implements INodeType { @@ -57,6 +63,11 @@ export class EditImage implements INodeType { value: 'composite', description: 'Composite image on top of another one', }, + { + name: 'Draw', + value: 'draw', + description: 'Draw on image', + }, { name: 'Get Information', value: 'information', @@ -96,6 +107,140 @@ export class EditImage implements INodeType { }, + // ---------------------------------- + // draw + // ---------------------------------- + { + displayName: 'Primitive', + name: 'primitive', + type: 'options', + displayOptions: { + show: { + operation: [ + 'draw', + ], + }, + }, + options: [ + { + name: 'Line', + value: 'line', + }, + { + name: 'Rectangle', + value: 'rectangle', + }, + ], + default: 'rectangle', + description: 'The primitive to draw.', + }, + { + displayName: 'Color', + name: 'color', + type: 'color', + default: '#ff000000', + typeOptions: { + showAlpha: true, + }, + displayOptions: { + show: { + operation: [ + 'draw', + ], + }, + }, + description: 'The color of the primitive to draw', + }, + { + displayName: 'Start Position X', + name: 'startPositionX', + type: 'number', + default: 50, + displayOptions: { + show: { + operation: [ + 'draw', + ], + primitive: [ + 'line', + 'rectangle', + ], + }, + }, + description: 'X (horizontal) start position of the primitive.', + }, + { + displayName: 'Start Position Y', + name: 'startPositionY', + type: 'number', + default: 50, + displayOptions: { + show: { + operation: [ + 'draw', + ], + primitive: [ + 'line', + 'rectangle', + ], + }, + }, + description: 'Y (horizontal) start position of the primitive.', + }, + { + displayName: 'End Position X', + name: 'endPositionX', + type: 'number', + default: 250, + displayOptions: { + show: { + operation: [ + 'draw', + ], + primitive: [ + 'line', + 'rectangle', + ], + }, + }, + description: 'X (horizontal) end position of the primitive.', + }, + { + displayName: 'End Position Y', + name: 'endPositionY', + type: 'number', + default: 250, + displayOptions: { + show: { + operation: [ + 'draw', + ], + primitive: [ + 'line', + 'rectangle', + ], + }, + }, + description: 'Y (horizontal) end position of the primitive.', + }, + { + displayName: 'Corner Radius', + name: 'cornerRadius', + type: 'number', + default: 0, + displayOptions: { + show: { + operation: [ + 'draw', + ], + primitive: [ + 'rectangle', + ], + }, + }, + description: 'The radius of the corner to create round corners.', + }, + // ---------------------------------- // text // ---------------------------------- @@ -501,7 +646,7 @@ export class EditImage implements INodeType { ], }, }, - description: 'The color to use for the background when image gets rotated by anything which is not a multiple of 90..', + description: 'The color to use for the background when image gets rotated by anything which is not a multiple of 90.', }, @@ -558,6 +703,23 @@ export class EditImage implements INodeType { default: '', description: 'File name to set in binary data.', }, + { + displayName: 'Font', + name: 'font', + type: 'options', + displayOptions: { + show: { + '/operation': [ + 'text', + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getFonts', + }, + default: 'default', + description: 'The font to use.', + }, { displayName: 'Format', name: 'format', @@ -613,6 +775,40 @@ export class EditImage implements INodeType { }; + methods = { + loadOptions: { + async getFonts(this: ILoadOptionsFunctions): Promise { + + // @ts-ignore + const files = await getSystemFonts(); + const returnData: INodePropertyOptions[] = []; + + files.forEach((file: string) => { + const pathParts = pathParse(file); + returnData.push({ + name: pathParts.name, + value: file, + }); + }); + + returnData.sort((a, b) => { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; + }); + + returnData.unshift({ + name: 'default', + value: 'default', + }); + + return returnData; + }, + + }, + }; + + async executeSingle(this: IExecuteSingleFunctions): Promise { const item = this.getInputData(); @@ -670,7 +866,24 @@ export class EditImage implements INodeType { const positionY = this.getNodeParameter('positionY') as number; gmInstance = gmInstance.crop(width, height, positionX, positionY); - } else if (operation === 'information') { + } else if (operation === 'draw') { + const startPositionX = this.getNodeParameter('startPositionX') as number; + const startPositionY = this.getNodeParameter('startPositionY') as number; + const endPositionX = this.getNodeParameter('endPositionX') as number; + const endPositionY = this.getNodeParameter('endPositionY') as number; + const primitive = this.getNodeParameter('primitive') as string; + const color = this.getNodeParameter('color') as string; + + gmInstance = gmInstance.fill(color); + + if (primitive === 'line') { + gmInstance = gmInstance.drawLine(startPositionX, startPositionY, endPositionX, endPositionY); + } else if (primitive === 'rectangle') { + const cornerRadius = this.getNodeParameter('cornerRadius') as number; + gmInstance = gmInstance.drawRectangle(startPositionX, startPositionY, endPositionX, endPositionY, cornerRadius || undefined); + } + + } else if (operation === 'information') { const imageData = await new Promise((resolve, reject) => { gmInstance = gmInstance.identify((error, imageData) => { if (error) { @@ -738,6 +951,10 @@ export class EditImage implements INodeType { // Combine the lines to a single string const renderText = lines.join('\n'); + if (options.font && options.font !== 'default') { + gmInstance = gmInstance.font(options.font as string); + } + gmInstance = gmInstance .fill(fontColor) .fontSize(fontSize) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 85b84dafd0..136dac82a8 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -453,6 +453,7 @@ "@types/redis": "^2.8.11", "@types/request-promise-native": "~1.0.15", "@types/ssh2-sftp-client": "^5.1.0", + "@types/tmp": "^0.2.0", "@types/uuid": "^3.4.6", "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", @@ -471,6 +472,7 @@ "cron": "^1.7.2", "eventsource": "^1.0.7", "formidable": "^1.2.1", + "get-system-fonts": "^2.0.2", "glob-promise": "^3.4.0", "gm": "^1.23.1", "imap-simple": "^4.3.0",