reusing canvas, passing imageBitmap instead

This commit is contained in:
2019-01-22 18:53:21 +01:00
parent abd29d2999
commit a8bced5b9e
22 changed files with 346 additions and 80 deletions

View File

@@ -0,0 +1,44 @@
import Node from '../Node';
import {waitForMedia} from '../../utils';
export default class Canvas2d extends Node {
constructor(name, inputDefinition, outputDefiniton, options) {
super(name,
Object.assign({
image: {
type: 'Image',
},
}, inputDefinition),
Object.assign({
image: {
type: 'Image',
}
}, outputDefiniton),
options
);
this.__canvas = document.createElement('canvas');
}
async __update() {
const values = {};
for (let name of Object.keys(this.in.variables)) {
values[name] = await waitForMedia(this.in[name].value);
}
const ctx = this.__canvas.getContext('2d');
if (this.__canvas.width > 0 && this.__canvas.height > 0) {
ctx.clearRect(0,0,this.__canvas.width, this.__canvas.height);
}
await this.render(values, this.__canvas, ctx);
}
/**
*
* @param {any} values
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} ctx
*/
async render(values, canvas, ctx) {
}
}

View File

@@ -0,0 +1,150 @@
import Canvas2d from './Canvas2d';
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
export default class Compose extends Canvas2d {
constructor(options) {
super('Compose', {
image: null,
fg: {
type: 'Image'
},
fgX: {
type: 'Number',
default: 0,
step: 1,
},
fgY: {
type: 'Number',
default: 0,
step: 1,
},
bg: {
type: 'Image',
},
bgX: {
type: 'Number',
default: 0,
step: 1,
},
bgY: {
type: 'Number',
default: 0,
step: 1,
},
width: {
type: 'Number',
default: 100,
step: 1,
min: 1
},
height: {
type: 'Number',
default: 100,
step: 1,
min: 1,
},
mode: {
type: 'String',
default: 'source-over',
enum: [
'source-over',
'source-in',
'source-out',
'source-atop',
'destination-over',
'destination-in',
'destination-out',
'destination-atop',
'lighter',
'copy',
'xor',
'multiply',
'screen',
'overlay',
'darken',
'lighten',
'color-dodge',
'color-burn',
'hard-light',
'soft-light',
'difference',
'exclusion',
'hue',
'saturation',
'color',
'luminosity',
]
}
}, {
image: {
type: 'Image'
}
}, options);
}
get width() {
return this.__in.width.value;
}
get height() {
return this.__in.height.value;
}
get fg() {
if (!this.__in.fg.value) {
return null;
} else {
const {width, height} = this.__in.fg.value;
return {
image: this.__in.fg.value,
top: this.__in.fgY.value || 0,
left: this.__in.fgX.value || 0,
width,
height,
};
}
}
get bg() {
if (!this.__in.bg.value) {
return null;
} else {
const {width, height} = this.__in.bg.value;
return {
image: this.__in.bg.value,
top: this.__in.bgY.value || 0,
left: this.__in.bgX.value || 0,
width,
height,
};
}
}
/**
* @param {any} values
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} ctx
*/
async render({width, height, mode}, canvas, ctx) {
if (!width || !height) {
return;
}
canvas.width = width;
canvas.height = height;
ctx.globalCompositeOperation = 'source-over';
if (this.bg) {
paintToCanvas(canvas, this.bg.image, this.bg);
}
ctx.globalCompositeOperation = mode;
if (this.fg) {
paintToCanvas(canvas, this.fg.image, this.fg);
}
this.__out.image.value = await createImageBitmap(canvas);
}
}

View File

@@ -0,0 +1,26 @@
import Node from './Canvas2d';
import {waitForMedia} from '../../utils';
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
export default class ImageIdentity extends Node {
constructor() {
super('ImageIdentity', {
}, {
});
}
async render({image}) {
if (!image) {
return;
}
const {width, height} = mediaSize(image);
const canvas = createCanvas(width, height);
paintToCanvas(canvas, image, {width, height, top: 0, left: 0});
const dataUrl = canvas.toDataURL()
const i = new Image();
i.src = dataUrl;
await waitForMedia(i);
this.__out.image.value = i;
}
}

View File

@@ -0,0 +1,59 @@
import Canvas2d from './Canvas2d';
import {waitForMedia} from '../../utils';
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
export default class Resize extends Canvas2d {
constructor() {
super('Resize', {
width: {
type: 'Number',
default: 100,
min: 1,
},
height: {
type: 'Number',
default: 100,
min: 1,
},
}, {});
}
/**
* @param {any} values
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} ctx
*/
async render({image: media, width, height}, canvas, ctx) {
await waitForMedia(media)
if (!media) {
return;
}
if (!width || !height) {
return;
}
canvas.width = width;
canvas.height = height;
const {width: srcWidth, height: srcHeight} = mediaSize(media);
const {width: destWidth, height: destHeight} = mediaSize(canvas);
const srcAspectRatio = (srcWidth / srcHeight);
const destAspectRatio = (destWidth / destHeight);
const newImage = {};
if (srcAspectRatio > destAspectRatio) {
newImage.width = destHeight * srcAspectRatio;
newImage.height = destHeight;
} else {
newImage.width = destWidth;
newImage.height = destWidth / srcAspectRatio;
}
newImage.top = (destHeight - newImage.height) / 2;
newImage.left = (destWidth - newImage.width) / 2;
paintToCanvas(canvas, media, newImage);
this.__out.image.value = await createImageBitmap(canvas);
}
}

View File

@@ -0,0 +1,49 @@
import Canvas2d from './Canvas2d';
import {waitForMedia} from '../../utils';
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
export default class Resize extends Canvas2d {
constructor() {
super('Text', {
image: null,
text: {
type: 'String',
default: '',
},
fontSize: {
type: 'Number',
min: 1,
default: 50,
},
font: {
type: 'String',
default: 'Arial',
},
width: {
type: 'Number',
default: 100,
min: 1,
},
height: {
type: 'Number',
default: 100,
min: 1,
},
color: {
type: 'Color',
default: '#FFFFFF'
}
}, {});
}
async render({font, fontSize, text, color, width, height}, canvas, ctx) {
canvas.width = width;
canvas.height = height;
ctx.font = `${fontSize}px ${font}`;
ctx.fillStyle = color;
ctx.textAlign = 'center';
ctx.fillText(text, canvas.width/2, canvas.height/2);
this.__out.image.value = await createImageBitmap(canvas);
}
}