2019-06-23 03:35:23 -07:00
import {
BINARY_ENCODING ,
2021-02-22 07:36:54 -08:00
IExecuteFunctions ,
2019-06-23 03:35:23 -07:00
} from 'n8n-core' ;
import {
IDataObject ,
2020-11-18 09:19:41 -08:00
ILoadOptionsFunctions ,
2019-06-23 03:35:23 -07:00
INodeExecutionData ,
2020-12-25 14:03:12 -08:00
INodeProperties ,
2020-11-18 09:19:41 -08:00
INodePropertyOptions ,
2019-06-23 03:35:23 -07:00
INodeType ,
INodeTypeDescription ,
2021-04-16 09:33:36 -07:00
NodeOperationError ,
2019-06-23 03:35:23 -07:00
} from 'n8n-workflow' ;
2022-04-08 14:32:08 -07:00
import gm from 'gm' ;
2020-11-09 02:26:46 -08:00
import { file } from 'tmp-promise' ;
2020-11-18 09:19:41 -08:00
import {
parse as pathParse ,
} from 'path' ;
2020-11-09 02:26:46 -08:00
import {
writeFile as fsWriteFile ,
} from 'fs' ;
import { promisify } from 'util' ;
const fsWriteFileAsync = promisify ( fsWriteFile ) ;
2022-05-12 10:12:18 -07:00
import getSystemFonts from 'get-system-fonts' ;
2019-06-23 03:35:23 -07:00
2020-12-25 14:03:12 -08:00
const nodeOperations : INodePropertyOptions [ ] = [
{
name : 'Blur' ,
value : 'blur' ,
description : 'Adds a blur to the image and so makes it less sharp' ,
} ,
{
name : 'Border' ,
value : 'border' ,
description : 'Adds a border to the image' ,
} ,
{
name : 'Composite' ,
value : 'composite' ,
description : 'Composite image on top of another one' ,
} ,
{
name : 'Create' ,
value : 'create' ,
description : 'Create a new image' ,
} ,
{
name : 'Crop' ,
value : 'crop' ,
description : 'Crops the image' ,
} ,
{
name : 'Draw' ,
value : 'draw' ,
description : 'Draw on image' ,
} ,
{
name : 'Rotate' ,
value : 'rotate' ,
description : 'Rotate image' ,
} ,
{
name : 'Resize' ,
value : 'resize' ,
description : 'Change the size of image' ,
} ,
{
name : 'Shear' ,
value : 'shear' ,
description : 'Shear image along the X or Y axis' ,
} ,
{
name : 'Text' ,
value : 'text' ,
description : 'Adds text to image' ,
} ,
2022-01-07 09:39:30 -08:00
{
name : 'Transparent' ,
value : 'transparent' ,
description : 'Make a color in image transparent' ,
} ,
2020-12-25 14:03:12 -08:00
] ;
const nodeOperationOptions : INodeProperties [ ] = [
// ----------------------------------
// create
// ----------------------------------
{
displayName : 'Background Color' ,
name : 'backgroundColor' ,
type : 'color' ,
default : '#ffffff00' ,
typeOptions : {
showAlpha : true ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
displayOptions : {
show : {
operation : [
'create' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'The background color of the image to create' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Image Width' ,
name : 'width' ,
type : 'number' ,
default : 50 ,
typeOptions : {
minValue : 1 ,
} ,
displayOptions : {
show : {
operation : [
'create' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'The width of the image to create' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Image Height' ,
name : 'height' ,
type : 'number' ,
default : 50 ,
typeOptions : {
minValue : 1 ,
} ,
displayOptions : {
show : {
operation : [
'create' ,
] ,
2020-11-19 01:48:46 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'The height of the image to create' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// draw
// ----------------------------------
{
displayName : 'Primitive' ,
name : 'primitive' ,
type : 'options' ,
displayOptions : {
show : {
operation : [
'draw' ,
] ,
2020-11-19 01:48:46 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
options : [
2021-11-13 00:39:22 -08:00
{
name : 'Circle' ,
value : 'circle' ,
} ,
2020-11-19 01:48:46 -08:00
{
2020-12-25 14:03:12 -08:00
name : 'Line' ,
value : 'line' ,
2020-11-19 01:48:46 -08:00
} ,
2020-11-18 09:19:41 -08:00
{
2020-12-25 14:03:12 -08:00
name : 'Rectangle' ,
value : 'rectangle' ,
} ,
] ,
default : 'rectangle' ,
2022-05-06 14:01:25 -07:00
description : 'The primitive to draw' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Color' ,
name : 'color' ,
type : 'color' ,
default : '#ff000000' ,
typeOptions : {
showAlpha : true ,
} ,
displayOptions : {
show : {
operation : [
'draw' ,
2020-11-18 09:19:41 -08:00
] ,
} ,
2020-12-25 14:03:12 -08:00
} ,
description : 'The color of the primitive to draw' ,
} ,
{
displayName : 'Start Position X' ,
name : 'startPositionX' ,
type : 'number' ,
default : 50 ,
displayOptions : {
show : {
operation : [
'draw' ,
] ,
primitive : [
2021-11-13 00:39:22 -08:00
'circle' ,
2020-12-25 14:03:12 -08:00
'line' ,
'rectangle' ,
] ,
2020-11-18 09:19:41 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'X (horizontal) start position of the primitive' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Start Position Y' ,
name : 'startPositionY' ,
type : 'number' ,
default : 50 ,
displayOptions : {
show : {
operation : [
'draw' ,
] ,
primitive : [
2021-11-13 00:39:22 -08:00
'circle' ,
2020-12-25 14:03:12 -08:00
'line' ,
'rectangle' ,
] ,
2020-11-18 09:19:41 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Y (horizontal) start position of the primitive' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'End Position X' ,
name : 'endPositionX' ,
type : 'number' ,
default : 250 ,
displayOptions : {
show : {
operation : [
'draw' ,
] ,
primitive : [
2021-11-13 00:39:22 -08:00
'circle' ,
2020-12-25 14:03:12 -08:00
'line' ,
'rectangle' ,
] ,
2020-11-18 09:19:41 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'X (horizontal) end position of the primitive' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'End Position Y' ,
name : 'endPositionY' ,
type : 'number' ,
default : 250 ,
displayOptions : {
show : {
operation : [
'draw' ,
] ,
primitive : [
2021-11-13 00:39:22 -08:00
'circle' ,
2020-12-25 14:03:12 -08:00
'line' ,
'rectangle' ,
] ,
2020-11-18 09:19:41 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Y (horizontal) end position of the primitive' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Corner Radius' ,
name : 'cornerRadius' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
operation : [
'draw' ,
] ,
primitive : [
'rectangle' ,
] ,
2020-11-18 09:19:41 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'The radius of the corner to create round corners' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// text
// ----------------------------------
{
displayName : 'Text' ,
name : 'text' ,
typeOptions : {
rows : 5 ,
} ,
type : 'string' ,
default : '' ,
placeholder : 'Text to render' ,
displayOptions : {
show : {
operation : [
'text' ,
] ,
2020-11-18 09:19:41 -08:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Text to write on the image' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Font Size' ,
name : 'fontSize' ,
type : 'number' ,
default : 18 ,
displayOptions : {
show : {
operation : [
'text' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Size of the text' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Font Color' ,
name : 'fontColor' ,
type : 'color' ,
default : '#000000' ,
displayOptions : {
show : {
operation : [
'text' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Color of the text' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Position X' ,
name : 'positionX' ,
type : 'number' ,
default : 50 ,
displayOptions : {
show : {
operation : [
'text' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'X (horizontal) position of the text' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Position Y' ,
name : 'positionY' ,
type : 'number' ,
default : 50 ,
displayOptions : {
show : {
operation : [
'text' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Y (vertical) position of the text' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Max Line Length' ,
name : 'lineLength' ,
type : 'number' ,
typeOptions : {
minValue : 1 ,
} ,
default : 80 ,
displayOptions : {
show : {
operation : [
'text' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'Max amount of characters in a line before a line-break should get added' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// blur
// ----------------------------------
{
displayName : 'Blur' ,
name : 'blur' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
maxValue : 1000 ,
} ,
default : 5 ,
displayOptions : {
show : {
operation : [
'blur' ,
] ,
} ,
} ,
description : 'How strong the blur should be' ,
} ,
{
displayName : 'Sigma' ,
name : 'sigma' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
maxValue : 1000 ,
} ,
default : 2 ,
displayOptions : {
show : {
operation : [
'blur' ,
] ,
} ,
} ,
description : 'The sigma of the blur' ,
} ,
// ----------------------------------
// border
// ----------------------------------
{
displayName : 'Border Width' ,
name : 'borderWidth' ,
type : 'number' ,
default : 10 ,
displayOptions : {
show : {
operation : [
'border' ,
] ,
} ,
} ,
description : 'The width of the border' ,
} ,
{
displayName : 'Border Height' ,
name : 'borderHeight' ,
type : 'number' ,
default : 10 ,
displayOptions : {
show : {
operation : [
'border' ,
] ,
} ,
} ,
description : 'The height of the border' ,
} ,
{
displayName : 'Border Color' ,
name : 'borderColor' ,
type : 'color' ,
default : '#000000' ,
displayOptions : {
show : {
operation : [
'border' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'Color of the border' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// composite
// ----------------------------------
{
displayName : 'Composite Image Property' ,
name : 'dataPropertyNameComposite' ,
type : 'string' ,
default : '' ,
placeholder : 'data2' ,
displayOptions : {
show : {
operation : [
'composite' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'The name of the binary property which contains the data of the image to composite on top of image which is found in Property Name' ,
2020-12-25 14:03:12 -08:00
} ,
2021-11-13 00:39:22 -08:00
{
displayName : 'Operator' ,
name : 'operator' ,
type : 'options' ,
displayOptions : {
show : {
operation : [
'composite' ,
] ,
} ,
} ,
options : [
{
name : 'Add' ,
value : 'Add' ,
} ,
{
name : 'Atop' ,
value : 'Atop' ,
} ,
{
name : 'Bumpmap' ,
value : 'Bumpmap' ,
} ,
{
name : 'Copy' ,
value : 'Copy' ,
} ,
{
name : 'Copy Black' ,
value : 'CopyBlack' ,
} ,
{
name : 'Copy Blue' ,
value : 'CopyBlue' ,
} ,
{
name : 'Copy Cyan' ,
value : 'CopyCyan' ,
} ,
{
name : 'Copy Green' ,
value : 'CopyGreen' ,
} ,
{
name : 'Copy Magenta' ,
value : 'CopyMagenta' ,
} ,
{
name : 'Copy Opacity' ,
value : 'CopyOpacity' ,
} ,
{
name : 'Copy Red' ,
value : 'CopyRed' ,
} ,
{
name : 'Copy Yellow' ,
value : 'CopyYellow' ,
} ,
{
name : 'Difference' ,
value : 'Difference' ,
} ,
{
name : 'Divide' ,
value : 'Divide' ,
} ,
{
name : 'In' ,
value : 'In' ,
} ,
{
name : 'Minus' ,
value : 'Minus' ,
} ,
{
name : 'Multiply' ,
value : 'Multiply' ,
} ,
{
name : 'Out' ,
value : 'Out' ,
} ,
{
name : 'Over' ,
value : 'Over' ,
} ,
{
name : 'Plus' ,
value : 'Plus' ,
} ,
{
name : 'Subtract' ,
value : 'Subtract' ,
} ,
{
name : 'Xor' ,
value : 'Xor' ,
} ,
] ,
default : 'Over' ,
2022-05-06 14:01:25 -07:00
description : 'The operator to use to combine the images' ,
2021-11-13 00:39:22 -08:00
} ,
2020-12-25 14:03:12 -08:00
{
displayName : 'Position X' ,
name : 'positionX' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
operation : [
'composite' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'X (horizontal) position of composite image' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Position Y' ,
name : 'positionY' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
operation : [
'composite' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'Y (vertical) position of composite image' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// crop
// ----------------------------------
{
displayName : 'Width' ,
name : 'width' ,
type : 'number' ,
default : 500 ,
displayOptions : {
show : {
operation : [
'crop' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
description : 'Crop width' ,
} ,
{
displayName : 'Height' ,
name : 'height' ,
type : 'number' ,
default : 500 ,
displayOptions : {
show : {
operation : [
'crop' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
description : 'Crop height' ,
} ,
{
displayName : 'Position X' ,
name : 'positionX' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
operation : [
'crop' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'X (horizontal) position to crop from' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Position Y' ,
name : 'positionY' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
operation : [
'crop' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Y (vertical) position to crop from' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// resize
// ----------------------------------
{
displayName : 'Width' ,
name : 'width' ,
type : 'number' ,
default : 500 ,
displayOptions : {
show : {
operation : [
'resize' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
description : 'New width of the image' ,
} ,
{
displayName : 'Height' ,
name : 'height' ,
type : 'number' ,
default : 500 ,
displayOptions : {
show : {
operation : [
'resize' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
description : 'New height of the image' ,
} ,
{
displayName : 'Option' ,
name : 'resizeOption' ,
type : 'options' ,
options : [
2019-06-23 03:35:23 -07:00
{
2020-12-25 14:03:12 -08:00
name : 'Ignore Aspect Ratio' ,
value : 'ignoreAspectRatio' ,
description : 'Ignore aspect ratio and resize exactly to specified values' ,
2019-06-23 03:35:23 -07:00
} ,
2020-11-18 02:08:50 -08:00
{
2022-06-03 10:23:49 -07:00
name : 'Maximum Area' ,
2020-12-25 14:03:12 -08:00
value : 'maximumArea' ,
description : 'Specified values are maximum area' ,
2020-11-18 02:08:50 -08:00
} ,
{
2020-12-25 14:03:12 -08:00
name : 'Minimum Area' ,
value : 'minimumArea' ,
description : 'Specified values are minimum area' ,
2020-11-18 02:08:50 -08:00
} ,
{
2022-06-03 10:23:49 -07:00
name : 'Only if Larger' ,
2020-12-25 14:03:12 -08:00
value : 'onlyIfLarger' ,
description : 'Resize only if image is larger than width or height' ,
2020-11-18 02:08:50 -08:00
} ,
2019-06-23 03:35:23 -07:00
{
2022-06-03 10:23:49 -07:00
name : 'Only if Smaller' ,
2020-12-25 14:03:12 -08:00
value : 'onlyIfSmaller' ,
description : 'Resize only if image is smaller than width or height' ,
2019-06-23 03:35:23 -07:00
} ,
{
2020-12-25 14:03:12 -08:00
name : 'Percent' ,
value : 'percent' ,
2022-05-06 14:01:25 -07:00
description : 'Width and height are specified in percents' ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
] ,
default : 'maximumArea' ,
displayOptions : {
show : {
operation : [
'resize' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'How to resize the image' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// rotate
// ----------------------------------
{
displayName : 'Rotate' ,
name : 'rotate' ,
type : 'number' ,
typeOptions : {
minValue : - 360 ,
maxValue : 360 ,
} ,
default : 0 ,
displayOptions : {
show : {
operation : [
'rotate' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
description : 'How much the image should be rotated' ,
} ,
{
displayName : 'Background Color' ,
name : 'backgroundColor' ,
type : 'color' ,
default : '#ffffffff' ,
typeOptions : {
showAlpha : true ,
} ,
displayOptions : {
show : {
operation : [
'rotate' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'The color to use for the background when image gets rotated by anything which is not a multiple of 90' ,
2020-12-25 14:03:12 -08:00
} ,
// ----------------------------------
// shear
// ----------------------------------
{
displayName : 'Degrees X' ,
name : 'degreesX' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
operation : [
'shear' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'X (horizontal) shear degrees' ,
2020-12-25 14:03:12 -08:00
} ,
{
displayName : 'Degrees Y' ,
name : 'degreesY' ,
type : 'number' ,
default : 0 ,
displayOptions : {
show : {
operation : [
'shear' ,
] ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
} ,
2022-05-06 14:01:25 -07:00
description : 'Y (vertical) shear degrees' ,
2020-12-25 14:03:12 -08:00
} ,
2022-01-07 09:39:30 -08:00
// ----------------------------------
// transparent
// ----------------------------------
{
displayName : 'Color' ,
name : 'color' ,
type : 'color' ,
default : '#ff0000' ,
displayOptions : {
show : {
operation : [
'transparent' ,
] ,
} ,
} ,
description : 'The color to make transparent' ,
} ,
2020-12-25 14:03:12 -08:00
] ;
export class EditImage implements INodeType {
description : INodeTypeDescription = {
displayName : 'Edit Image' ,
name : 'editImage' ,
icon : 'fa:image' ,
group : [ 'transform' ] ,
version : 1 ,
description : 'Edits an image like blur, resize or adding border and text' ,
defaults : {
name : 'Edit Image' ,
color : '#553399' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
properties : [
2019-06-23 03:35:23 -07:00
{
2020-12-25 14:03:12 -08:00
displayName : 'Operation' ,
name : 'operation' ,
2019-06-23 03:35:23 -07:00
type : 'options' ,
2022-05-20 14:47:24 -07:00
noDataExpression : true ,
2019-06-23 03:35:23 -07:00
options : [
{
2020-12-25 14:03:12 -08:00
name : 'Get Information' ,
value : 'information' ,
description : 'Returns image information like resolution' ,
2019-06-23 03:35:23 -07:00
} ,
2020-11-09 02:26:46 -08:00
{
2020-12-25 14:03:12 -08:00
name : 'Multi Step' ,
value : 'multiStep' ,
description : 'Perform multiple operations' ,
2019-06-23 03:35:23 -07:00
} ,
2020-12-25 14:03:12 -08:00
. . . nodeOperations ,
] . sort ( ( a , b ) = > {
if ( ( a as INodePropertyOptions ) . name . toLowerCase ( ) < ( b as INodePropertyOptions ) . name . toLowerCase ( ) ) { return - 1 ; }
if ( ( a as INodePropertyOptions ) . name . toLowerCase ( ) > ( b as INodePropertyOptions ) . name . toLowerCase ( ) ) { return 1 ; }
return 0 ;
} ) as INodePropertyOptions [ ] ,
default : 'border' ,
2019-06-23 03:35:23 -07:00
} ,
{
2020-12-25 14:03:12 -08:00
displayName : 'Property Name' ,
name : 'dataPropertyName' ,
type : 'string' ,
default : 'data' ,
2022-05-06 14:01:25 -07:00
description : 'Name of the binary property in which the image data can be found' ,
2019-06-23 03:35:23 -07:00
} ,
2020-05-26 15:43:19 -07:00
2020-11-09 02:26:46 -08:00
// ----------------------------------
2020-12-25 14:03:12 -08:00
// multiStep
2020-11-09 02:26:46 -08:00
// ----------------------------------
{
2020-12-25 14:03:12 -08:00
displayName : 'Operations' ,
name : 'operations' ,
placeholder : 'Add Operation' ,
type : 'fixedCollection' ,
typeOptions : {
multipleValues : true ,
2020-12-26 15:17:16 -08:00
sortable : true ,
2020-12-25 14:03:12 -08:00
} ,
2020-11-09 02:26:46 -08:00
displayOptions : {
show : {
operation : [
2020-12-25 14:03:12 -08:00
'multiStep' ,
2020-11-09 02:26:46 -08:00
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'The operations to perform' ,
2020-12-25 14:03:12 -08:00
default : { } ,
options : [
{
name : 'operations' ,
displayName : 'Operations' ,
values : [
{
displayName : 'Operation' ,
name : 'operation' ,
type : 'options' ,
2022-05-20 14:47:24 -07:00
noDataExpression : true ,
2020-12-25 14:03:12 -08:00
options : nodeOperations ,
default : '' ,
} ,
. . . nodeOperationOptions ,
{
2022-06-03 10:23:49 -07:00
displayName : 'Font Name or ID' ,
2020-12-25 14:03:12 -08:00
name : 'font' ,
type : 'options' ,
displayOptions : {
show : {
'operation' : [
'text' ,
] ,
} ,
} ,
typeOptions : {
loadOptionsMethod : 'getFonts' ,
} ,
default : 'default' ,
2022-06-03 10:23:49 -07:00
description : 'The font to use. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/nodes/expressions.html#expressions">expression</a>.' ,
2020-12-25 14:03:12 -08:00
} ,
2020-11-09 02:26:46 -08:00
] ,
} ,
2020-12-25 14:03:12 -08:00
] ,
2020-11-09 02:26:46 -08:00
} ,
2020-12-25 14:03:12 -08:00
. . . nodeOperationOptions ,
2020-05-26 15:43:19 -07:00
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
displayOptions : {
hide : {
operation : [
'information' ,
] ,
} ,
} ,
options : [
{
displayName : 'File Name' ,
name : 'fileName' ,
type : 'string' ,
default : '' ,
2022-05-06 14:01:25 -07:00
description : 'File name to set in binary data' ,
2020-05-26 15:43:19 -07:00
} ,
2020-11-18 09:19:41 -08:00
{
2022-06-03 10:23:49 -07:00
displayName : 'Font Name or ID' ,
2020-11-18 09:19:41 -08:00
name : 'font' ,
type : 'options' ,
displayOptions : {
show : {
'/operation' : [
'text' ,
] ,
} ,
} ,
typeOptions : {
loadOptionsMethod : 'getFonts' ,
} ,
default : 'default' ,
2022-06-03 10:23:49 -07:00
description : 'The font to use. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/nodes/expressions.html#expressions">expression</a>.' ,
2020-11-18 09:19:41 -08:00
} ,
2020-05-26 15:43:19 -07:00
{
displayName : 'Format' ,
name : 'format' ,
type : 'options' ,
options : [
{
name : 'bmp' ,
value : 'bmp' ,
} ,
{
name : 'gif' ,
value : 'gif' ,
} ,
{
name : 'jpeg' ,
value : 'jpeg' ,
} ,
{
name : 'png' ,
value : 'png' ,
} ,
{
name : 'tiff' ,
value : 'tiff' ,
} ,
] ,
default : 'jpeg' ,
2022-05-06 14:01:25 -07:00
description : 'Set the output image format' ,
2020-05-26 15:43:19 -07:00
} ,
{
displayName : 'Quality' ,
name : 'quality' ,
type : 'number' ,
typeOptions : {
minValue : 0 ,
maxValue : 100 ,
} ,
default : 100 ,
displayOptions : {
show : {
format : [
'jpeg' ,
'png' ,
'tiff' ,
] ,
} ,
} ,
2022-05-06 14:01:25 -07:00
description : 'Sets the jpeg|png|tiff compression level from 0 to 100 (best)' ,
2020-05-26 15:43:19 -07:00
} ,
] ,
} ,
2020-10-22 06:46:03 -07:00
] ,
2019-06-23 03:35:23 -07:00
} ;
2020-11-18 09:19:41 -08:00
methods = {
loadOptions : {
async getFonts ( this : ILoadOptionsFunctions ) : Promise < INodePropertyOptions [ ] > {
// @ts-ignore
const files = await getSystemFonts ( ) ;
const returnData : INodePropertyOptions [ ] = [ ] ;
files . forEach ( ( file : string ) = > {
const pathParts = pathParse ( file ) ;
2020-11-18 15:17:29 -08:00
if ( ! pathParts . ext ) {
return ;
}
2020-11-18 09:19:41 -08:00
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 ;
} ,
} ,
} ;
2021-03-29 02:20:10 -07:00
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
2021-02-22 07:36:54 -08:00
const items = this . getInputData ( ) ;
2021-03-29 02:20:10 -07:00
2021-02-22 07:36:54 -08:00
const returnData : INodeExecutionData [ ] = [ ] ;
2022-04-22 09:29:51 -07:00
const length = items . length ;
2021-02-22 07:36:54 -08:00
let item : INodeExecutionData ;
2020-11-18 09:19:41 -08:00
2021-02-22 07:36:54 -08:00
for ( let itemIndex = 0 ; itemIndex < length ; itemIndex ++ ) {
2020-12-25 14:03:12 -08:00
2021-07-19 23:58:54 -07:00
try {
item = items [ itemIndex ] ;
const operation = this . getNodeParameter ( 'operation' , itemIndex ) as string ;
const dataPropertyName = this . getNodeParameter ( 'dataPropertyName' , itemIndex ) as string ;
const options = this . getNodeParameter ( 'options' , itemIndex , { } ) as IDataObject ;
const cleanupFunctions : Array < ( ) = > void > = [ ] ;
let gmInstance : gm.State ;
const requiredOperationParameters : {
[ key : string ] : string [ ] ,
} = {
blur : [
'blur' ,
'sigma' ,
] ,
border : [
'borderColor' ,
'borderWidth' ,
'borderHeight' ,
] ,
create : [
'backgroundColor' ,
'height' ,
'width' ,
] ,
crop : [
'height' ,
'positionX' ,
'positionY' ,
'width' ,
] ,
composite : [
'dataPropertyNameComposite' ,
2021-11-25 14:51:34 -08:00
'operator' ,
2021-07-19 23:58:54 -07:00
'positionX' ,
'positionY' ,
] ,
draw : [
'color' ,
'cornerRadius' ,
'endPositionX' ,
'endPositionY' ,
'primitive' ,
'startPositionX' ,
'startPositionY' ,
] ,
information : [ ] ,
resize : [
'height' ,
'resizeOption' ,
'width' ,
] ,
rotate : [
'backgroundColor' ,
'rotate' ,
] ,
shear : [
'degreesX' ,
'degreesY' ,
] ,
text : [
'font' ,
'fontColor' ,
'fontSize' ,
'lineLength' ,
'positionX' ,
'positionY' ,
'text' ,
] ,
} ;
2019-06-23 03:35:23 -07:00
2021-07-19 23:58:54 -07:00
let operations : IDataObject [ ] = [ ] ;
if ( operation === 'multiStep' ) {
// Operation parameters are already in the correct format
const operationsData = this . getNodeParameter ( 'operations' , itemIndex , { operations : [ ] } ) as IDataObject ;
operations = operationsData . operations as IDataObject [ ] ;
} else {
// Operation parameters have to first get collected
const operationParameters : IDataObject = { } ;
requiredOperationParameters [ operation ] . forEach ( parameterName = > {
try {
operationParameters [ parameterName ] = this . getNodeParameter ( parameterName , itemIndex ) ;
} catch ( error ) { }
} ) ;
2020-12-25 14:03:12 -08:00
2021-07-19 23:58:54 -07:00
operations = [
{
operation ,
. . . operationParameters ,
} ,
] ;
}
2020-12-25 14:03:12 -08:00
2021-07-19 23:58:54 -07:00
if ( operations [ 0 ] . operation !== 'create' ) {
// "create" generates a new image so does not require any incoming data.
if ( item . binary === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , 'Item does not contain any binary data.' ) ;
}
2020-12-25 14:03:12 -08:00
2021-07-19 23:58:54 -07:00
if ( item . binary [ dataPropertyName as string ] === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , ` Item does not contain any binary data with the name " ${ dataPropertyName } ". ` ) ;
}
2020-12-25 14:44:58 -08:00
2022-01-03 13:42:42 -08:00
const binaryDataBuffer = await this . helpers . getBinaryDataBuffer ( itemIndex , dataPropertyName ) ;
gmInstance = gm ( binaryDataBuffer ) ;
2021-07-19 23:58:54 -07:00
gmInstance = gmInstance . background ( 'transparent' ) ;
2020-12-25 14:03:12 -08:00
}
2022-01-07 09:38:35 -08:00
const newItem : INodeExecutionData = {
json : item.json ,
binary : { } ,
2022-06-03 08:25:07 -07:00
pairedItem : {
item : itemIndex ,
} ,
2022-01-07 09:38:35 -08:00
} ;
2021-07-19 23:58:54 -07:00
if ( operation === 'information' ) {
// Just return the information
const imageData = await new Promise < IDataObject > ( ( resolve , reject ) = > {
gmInstance = gmInstance . identify ( ( error , imageData ) = > {
if ( error ) {
reject ( error ) ;
return ;
}
resolve ( imageData as unknown as IDataObject ) ;
} ) ;
2020-12-25 14:03:12 -08:00
} ) ;
2019-06-23 03:35:23 -07:00
2022-01-07 09:38:35 -08:00
newItem . json = imageData ;
2021-07-19 23:58:54 -07:00
}
2019-06-23 03:35:23 -07:00
2021-07-19 23:58:54 -07:00
for ( let i = 0 ; i < operations . length ; i ++ ) {
const operationData = operations [ i ] ;
if ( operationData . operation === 'blur' ) {
gmInstance = gmInstance ! . blur ( operationData . blur as number , operationData . sigma as number ) ;
} else if ( operationData . operation === 'border' ) {
gmInstance = gmInstance ! . borderColor ( operationData . borderColor as string ) . border ( operationData . borderWidth as number , operationData . borderHeight as number ) ;
} else if ( operationData . operation === 'composite' ) {
const positionX = operationData . positionX as number ;
const positionY = operationData . positionY as number ;
2021-11-13 00:39:22 -08:00
const operator = operationData . operator as string ;
2020-11-18 09:19:41 -08:00
2021-07-19 23:58:54 -07:00
const geometryString = ( positionX >= 0 ? '+' : '' ) + positionX + ( positionY >= 0 ? '+' : '' ) + positionY ;
2021-02-22 07:36:54 -08:00
2021-07-19 23:58:54 -07:00
if ( item . binary ! [ operationData . dataPropertyNameComposite as string ] === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , ` Item does not contain any binary data with the name " ${ operationData . dataPropertyNameComposite } ". ` ) ;
}
2021-02-22 07:36:54 -08:00
2021-07-19 23:58:54 -07:00
const { fd , path , cleanup } = await file ( ) ;
cleanupFunctions . push ( cleanup ) ;
2022-01-03 13:42:42 -08:00
const binaryDataBuffer = await this . helpers . getBinaryDataBuffer ( itemIndex , operationData . dataPropertyNameComposite as string ) ;
await fsWriteFileAsync ( fd , binaryDataBuffer ) ;
2021-02-22 07:36:54 -08:00
2021-07-19 23:58:54 -07:00
if ( operations [ 0 ] . operation === 'create' ) {
// It seems like if the image gets created newly we have to create a new gm instance
// else it fails for some reason
2021-11-13 00:39:22 -08:00
gmInstance = gm ( gmInstance ! . stream ( 'png' ) ) . compose ( operator ) . geometry ( geometryString ) . composite ( path ) ;
2021-07-19 23:58:54 -07:00
} else {
2021-11-13 00:39:22 -08:00
gmInstance = gmInstance ! . compose ( operator ) . geometry ( geometryString ) . composite ( path ) ;
2021-02-22 07:36:54 -08:00
}
2021-07-19 23:58:54 -07:00
if ( operations . length !== i + 1 ) {
// If there are other operations after the current one create a new gm instance
// because else things do get messed up
gmInstance = gm ( gmInstance . stream ( ) ) ;
}
} else if ( operationData . operation === 'create' ) {
gmInstance = gm ( operationData . width as number , operationData . height as number , operationData . backgroundColor as string ) ;
if ( ! options . format ) {
options . format = 'png' ;
2021-02-22 07:36:54 -08:00
}
2021-07-19 23:58:54 -07:00
} else if ( operationData . operation === 'crop' ) {
gmInstance = gmInstance ! . crop ( operationData . width as number , operationData . height as number , operationData . positionX as number , operationData . positionY as number ) ;
} else if ( operationData . operation === 'draw' ) {
gmInstance = gmInstance ! . fill ( operationData . color as string ) ;
if ( operationData . primitive === 'line' ) {
gmInstance = gmInstance . drawLine ( operationData . startPositionX as number , operationData . startPositionY as number , operationData . endPositionX as number , operationData . endPositionY as number ) ;
2021-11-13 00:39:22 -08:00
} else if ( operationData . primitive === 'circle' ) {
gmInstance = gmInstance . drawCircle ( operationData . startPositionX as number , operationData . startPositionY as number , operationData . endPositionX as number , operationData . endPositionY as number ) ;
2021-07-19 23:58:54 -07:00
} else if ( operationData . primitive === 'rectangle' ) {
gmInstance = gmInstance . drawRectangle ( operationData . startPositionX as number , operationData . startPositionY as number , operationData . endPositionX as number , operationData . endPositionY as number , operationData . cornerRadius as number || undefined ) ;
}
} else if ( operationData . operation === 'resize' ) {
const resizeOption = operationData . resizeOption as string ;
// By default use "maximumArea"
let option : gm.ResizeOption = '@' ;
if ( resizeOption === 'ignoreAspectRatio' ) {
option = '!' ;
} else if ( resizeOption === 'minimumArea' ) {
option = '^' ;
} else if ( resizeOption === 'onlyIfSmaller' ) {
option = '<' ;
} else if ( resizeOption === 'onlyIfLarger' ) {
option = '>' ;
} else if ( resizeOption === 'percent' ) {
option = '%' ;
}
gmInstance = gmInstance ! . resize ( operationData . width as number , operationData . height as number , option ) ;
} else if ( operationData . operation === 'rotate' ) {
gmInstance = gmInstance ! . rotate ( operationData . backgroundColor as string , operationData . rotate as number ) ;
} else if ( operationData . operation === 'shear' ) {
gmInstance = gmInstance ! . shear ( operationData . degreesX as number , operationData . degreesY as number ) ;
} else if ( operationData . operation === 'text' ) {
// Split the text in multiple lines
const lines : string [ ] = [ ] ;
let currentLine = '' ;
( operationData . text as string ) . split ( '\n' ) . forEach ( ( textLine : string ) = > {
textLine . split ( ' ' ) . forEach ( ( textPart : string ) = > {
if ( ( currentLine . length + textPart . length + 1 ) > ( operationData . lineLength as number ) ) {
lines . push ( currentLine . trim ( ) ) ;
currentLine = ` ${ textPart } ` ;
return ;
}
currentLine += ` ${ textPart } ` ;
} ) ;
lines . push ( currentLine . trim ( ) ) ;
currentLine = '' ;
2021-02-22 07:36:54 -08:00
} ) ;
2021-07-19 23:58:54 -07:00
// Combine the lines to a single string
const renderText = lines . join ( '\n' ) ;
2021-02-22 07:36:54 -08:00
2021-07-19 23:58:54 -07:00
const font = options . font || operationData . font ;
2019-06-23 03:35:23 -07:00
2021-07-19 23:58:54 -07:00
if ( font && font !== 'default' ) {
gmInstance = gmInstance ! . font ( font as string ) ;
}
2021-02-22 07:36:54 -08:00
2021-07-19 23:58:54 -07:00
gmInstance = gmInstance !
. fill ( operationData . fontColor as string )
. fontSize ( operationData . fontSize as number )
. drawText ( operationData . positionX as number , operationData . positionY as number , renderText ) ;
2022-01-07 09:39:30 -08:00
} else if ( operationData . operation === 'transparent' ) {
gmInstance = gmInstance ! . transparent ( operationData . color as string ) ;
}
2021-02-22 07:36:54 -08:00
}
2020-12-26 12:30:15 -08:00
2021-07-19 23:58:54 -07:00
if ( item . binary !== undefined ) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object . assign ( newItem . binary , item . binary ) ;
// Make a deep copy of the binary data we change
if ( newItem . binary ! [ dataPropertyName as string ] ) {
newItem . binary ! [ dataPropertyName as string ] = JSON . parse ( JSON . stringify ( newItem . binary ! [ dataPropertyName as string ] ) ) ;
}
2021-02-22 07:36:54 -08:00
}
2020-05-26 15:43:19 -07:00
2021-07-19 23:58:54 -07:00
if ( newItem . binary ! [ dataPropertyName as string ] === undefined ) {
newItem . binary ! [ dataPropertyName as string ] = {
data : '' ,
mimeType : '' ,
} ;
}
2020-05-26 15:43:19 -07:00
2021-07-19 23:58:54 -07:00
if ( options . quality !== undefined ) {
gmInstance = gmInstance ! . quality ( options . quality as number ) ;
}
2021-02-22 07:36:54 -08:00
2021-07-19 23:58:54 -07:00
if ( options . format !== undefined ) {
gmInstance = gmInstance ! . setFormat ( options . format as string ) ;
newItem . binary ! [ dataPropertyName as string ] . fileExtension = options . format as string ;
newItem . binary ! [ dataPropertyName as string ] . mimeType = ` image/ ${ options . format } ` ;
const fileName = newItem . binary ! [ dataPropertyName as string ] . fileName ;
if ( fileName && fileName . includes ( '.' ) ) {
newItem . binary ! [ dataPropertyName as string ] . fileName = fileName . split ( '.' ) . slice ( 0 , - 1 ) . join ( '.' ) + '.' + options . format ;
}
2021-02-22 07:36:54 -08:00
}
2020-05-26 15:43:19 -07:00
2021-07-19 23:58:54 -07:00
if ( options . fileName !== undefined ) {
newItem . binary ! [ dataPropertyName as string ] . fileName = options . fileName as string ;
}
2020-11-18 02:08:50 -08:00
2021-07-19 23:58:54 -07:00
returnData . push ( await ( new Promise < INodeExecutionData > ( ( resolve , reject ) = > {
gmInstance
2022-01-03 13:42:42 -08:00
. toBuffer ( async ( error : Error | null , buffer : Buffer ) = > {
2021-07-19 23:58:54 -07:00
cleanupFunctions . forEach ( async cleanup = > await cleanup ( ) ) ;
2019-06-23 03:35:23 -07:00
2021-07-19 23:58:54 -07:00
if ( error ) {
return reject ( error ) ;
}
2019-06-23 03:35:23 -07:00
2022-05-10 01:47:37 -07:00
const binaryData = await this . helpers . prepareBinaryData ( Buffer . from ( buffer ) ) ;
newItem . binary ! [ dataPropertyName as string ] = {
2022-05-10 02:01:03 -07:00
. . . newItem . binary ! [ dataPropertyName as string ] , . . . binaryData ,
} ;
2021-02-22 07:36:54 -08:00
2021-07-19 23:58:54 -07:00
return resolve ( newItem ) ;
} ) ;
} ) ) ) ;
2021-03-29 02:20:10 -07:00
2021-07-19 23:58:54 -07:00
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
2022-06-03 08:25:07 -07:00
returnData . push ( {
json : {
error : error.message ,
} ,
pairedItem : {
item : itemIndex ,
} ,
} ) ;
2021-07-19 23:58:54 -07:00
continue ;
}
throw error ;
}
2021-02-22 07:36:54 -08:00
}
return this . prepareOutputData ( returnData ) ;
2019-06-23 03:35:23 -07:00
}
}