diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue
index 893c61aa33..336e27041a 100644
--- a/packages/editor-ui/src/components/ParameterInput.vue
+++ b/packages/editor-ui/src/components/ParameterInput.vue
@@ -82,8 +82,8 @@
-
-
+
+
@@ -213,6 +213,10 @@ export default mixins(
this.loadRemoteParameterOptions();
},
value () {
+ if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true) {
+ // Do not set for color with alpha else wrong value gets displayed in field
+ return;
+ }
this.tempValue = this.displayValue as string;
},
},
@@ -274,6 +278,18 @@ export default mixins(
returnValue = this.expressionValueComputed;
}
+ if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && returnValue.charAt(0) === '#') {
+ // Convert the value to rgba that el-color-picker can display it correctly
+ const bigint = parseInt(returnValue.slice(1), 16);
+ const h = [];
+ h.push((bigint >> 24) & 255);
+ h.push((bigint >> 16) & 255);
+ h.push((bigint >> 8) & 255);
+ h.push((255 - bigint & 255) / 255);
+
+ returnValue = 'rgba('+h.join()+')';
+ }
+
if (returnValue !== undefined && returnValue !== null && this.parameter.type === 'string') {
const rows = this.getArgument('rows');
if (rows === undefined || rows === 1) {
@@ -537,14 +553,35 @@ export default mixins(
// Set focus on field
setTimeout(() => {
// @ts-ignore
- (this.$refs.inputField.$el.querySelector('input') as HTMLInputElement).focus();
+ if (this.$refs.inputField.$el) {
+ // @ts-ignore
+ (this.$refs.inputField.$el.querySelector('input') as HTMLInputElement).focus();
+ }
});
},
+ rgbaToHex (value: string): string | null {
+ // Convert rgba to hex from: https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
+ const valueMatch = (value as string).match(/^rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+(\.\d+)?)\)$/);
+ if (valueMatch === null) {
+ // TODO: Display something if value is not valid
+ return null;
+ }
+ const [r, g, b, a] = valueMatch.splice(1, 4).map(v => Number(v));
+ return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) + ((1 << 8) + Math.floor((1-a)*255)).toString(16).slice(1);
+ },
valueChanged (value: string | number | boolean | Date | null) {
if (value instanceof Date) {
value = value.toISOString();
}
+ if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && value !== null && value.toString().charAt(0) !== '#') {
+ const newValue = this.rgbaToHex(value as string);
+ if (newValue !== null) {
+ this.tempValue = newValue;
+ value = newValue;
+ }
+ }
+
const parameterData = {
node: this.node !== null ? this.node.name : this.nodeName,
name: this.path,
@@ -570,6 +607,13 @@ export default mixins(
this.nodeName = this.node.name;
}
+ if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && this.displayValue !== null && this.displayValue.toString().charAt(0) !== '#') {
+ const newValue = this.rgbaToHex(this.displayValue as string);
+ if (newValue !== null) {
+ this.tempValue = newValue;
+ }
+ }
+
if (this.remoteMethod !== undefined && this.node !== null) {
// Make sure to load the parameter options
// directly and whenever the credentials change
diff --git a/packages/nodes-base/nodes/EditImage.node.ts b/packages/nodes-base/nodes/EditImage.node.ts
index 9e10000ffd..5c3b9c53aa 100644
--- a/packages/nodes-base/nodes/EditImage.node.ts
+++ b/packages/nodes-base/nodes/EditImage.node.ts
@@ -9,6 +9,12 @@ import {
INodeTypeDescription,
} from 'n8n-workflow';
import * as gm from 'gm';
+import { file } from 'tmp-promise';
+import {
+ writeFile as fsWriteFile,
+} from 'fs';
+import { promisify } from 'util';
+const fsWriteFileAsync = promisify(fsWriteFile);
export class EditImage implements INodeType {
@@ -61,6 +67,11 @@ export class EditImage implements INodeType {
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',
@@ -385,6 +396,11 @@ export class EditImage implements INodeType {
value: 'onlyIfSmaller',
description: 'Resize only if image is smaller than width or height',
},
+ {
+ name: 'Percent',
+ value: 'percent',
+ description: 'Width and height are specified in percents.',
+ },
],
default: 'maximumArea',
displayOptions: {
@@ -422,7 +438,10 @@ export class EditImage implements INodeType {
displayName: 'Background Color',
name: 'backgroundColor',
type: 'color',
- default: '#ffffff',
+ default: '#ffffffff',
+ typeOptions: {
+ showAlpha: true,
+ },
displayOptions: {
show: {
operation: [
@@ -433,6 +452,39 @@ 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..',
},
+
+ // ----------------------------------
+ // shear
+ // ----------------------------------
+ {
+ displayName: 'Degrees X',
+ name: 'degreesX',
+ type: 'number',
+ default: 0,
+ displayOptions: {
+ show: {
+ operation: [
+ 'shear',
+ ],
+ },
+ },
+ description: 'X (horizontal) shear degrees.',
+ },
+ {
+ displayName: 'Degrees Y',
+ name: 'degreesY',
+ type: 'number',
+ default: 0,
+ displayOptions: {
+ show: {
+ operation: [
+ 'shear',
+ ],
+ },
+ },
+ description: 'Y (vertical) shear degrees.',
+ },
+
{
displayName: 'Options',
name: 'options',
@@ -503,7 +555,6 @@ export class EditImage implements INodeType {
},
description: 'Sets the jpeg|png|tiff compression level from 0 to 100 (best).',
},
-
],
},
],
@@ -529,6 +580,8 @@ export class EditImage implements INodeType {
let gmInstance = gm(Buffer.from(item.binary![dataPropertyName as string].data, BINARY_ENCODING));
+ gmInstance = gmInstance.background('transparent');
+
if (operation === 'blur') {
const blur = this.getNodeParameter('blur') as number;
const sigma = this.getNodeParameter('sigma') as number;
@@ -574,6 +627,8 @@ export class EditImage implements INodeType {
option = '<';
} else if (resizeOption === 'onlyIfLarger') {
option = '>';
+ } else if (resizeOption === 'percent') {
+ option = '%';
}
gmInstance = gmInstance.resize(width, height, option);
@@ -581,6 +636,10 @@ export class EditImage implements INodeType {
const rotate = this.getNodeParameter('rotate') as number;
const backgroundColor = this.getNodeParameter('backgroundColor') as string;
gmInstance = gmInstance.rotate(backgroundColor, rotate);
+ } else if (operation === 'shear') {
+ const xDegrees = this.getNodeParameter('degreesX') as number;
+ const yDegress = this.getNodeParameter('degreesY') as number;
+ gmInstance = gmInstance.shear(xDegrees, yDegress);
} else if (operation === 'text') {
const fontColor = this.getNodeParameter('fontColor') as string;
const fontSize = this.getNodeParameter('fontSize') as number;
@@ -624,6 +683,8 @@ export class EditImage implements INodeType {
// 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
+ newItem.binary![dataPropertyName as string] = JSON.parse(JSON.stringify(newItem.binary![dataPropertyName as string]));
}
if (options.quality !== undefined) {
diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts
index bf0fc63e13..c6e42e0d28 100644
--- a/packages/workflow/src/Interfaces.ts
+++ b/packages/workflow/src/Interfaces.ts
@@ -408,6 +408,7 @@ export interface INodePropertyTypeOptions {
numberStepSize?: number; // Supported by: number
password?: boolean; // Supported by: string
rows?: number; // Supported by: string
+ showAlpha?: boolean; // Supported by: color
[key: string]: boolean | number | string | EditorTypes | undefined | string[];
}