History of changes of HTML5 canvas - history

I am developing HTML5 based paint application using canvas, and now I want to do history of all canvas changes. I mean a history of user actions.
How can I do this?

Saving the data urls into an Array: http://jsfiddle.net/eGjak/54/.
var cv = $('#cv').get(0);
var ctx = cv.getContext('2d');
var history = [];
$("#b1").click(function() {
history.push(cv.toDataURL());
ctx.beginPath();
ctx.arc(Math.random() * 200 + 100,
Math.random() * 200 + 100,
Math.random() * 200,
0,
2 * Math.PI);
ctx.stroke();
});
$("#b2").click(function() {
ctx.beginPath();
var img = new Image;
img.onload = function() {
ctx.clearRect(0, 0, 400, 400);
ctx.drawImage(img, 0, 0);
};
img.src = history.pop();
});

What you may try to do is creating an array of events, and fill it each times your onclick (or the events wich interest you) happen. this way, you have an history of all user inputs.
You may also instead of storing the events only, store with them the tool being used, to simplify the redrawing at a previous state.
Is that what you wanted ?

Related

Setting a welcome channel Discord.js?

Now there are a lot of tutorials on setting up welcome messages, and sending it once a member joins the guild, but they are only for custom servers, because look:
const applyText = (canvas, text) => {
const ctx = canvas.getContext('2d');
let fontSize = 70;
do {
ctx.font = `${fontSize -= 10}px sans-serif`;
} while (ctx.measureText(text).width > canvas.width - 300);
return ctx.font;
};
client.on('guildMemberAdd', async member => {
const channel = member.guild.channels.cache.find(ch => ch.name === '🚪welcome');
if (!channel) return;
const canvas = Canvas.createCanvas(700, 250);
const ctx = canvas.getContext('2d');
const background = await Canvas.loadImage('./GANG.jpg');
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#74037b';
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = '45px Masked Hero Demo';
ctx.fillStyle = '#000000';
ctx.fillText('WELCOME,', canvas.width / 2.4, canvas.height / 3.5);
ctx.font = '45px Masked Hero Demo';
ctx.fillStyle = '#ffffff';
ctx.fillText('WELCOME,', canvas.width / 2.37, canvas.height / 3.6);
ctx.font = applyText(canvas, `${member.displayName}!`);
ctx.fillStyle = '#000000';
ctx.fillText(`${member.displayName}!`, canvas.width / 2.5, canvas.height / 1.57);
ctx.font = applyText(canvas, `${member.displayName}!`);
ctx.fillStyle = '#ffffff';
ctx.fillText(`${member.displayName}!`, canvas.width / 2.47, canvas.height / 1.6);
ctx.beginPath();
ctx.arc(125, 125, 100, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
const avatar = await Canvas.loadImage(member.user.displayAvatarURL({ format: 'jpg' }));
ctx.drawImage(avatar, 25, 25, 200, 200);
const attachment = new Discord.MessageAttachment(canvas.toBuffer(), 'GANG.jpg');
channel.send(`Welcome to the server, ${member}! Make sure to check info at <#743160115833864263>`, attachment);
});
At the part where it says 🚪welcome, that means any channel matching that name is where the message will send to.
But this would only work using a custom bot, and since I want everyone to use this, depending since they wont use 🚪welcome as their welcome channel name. How do I make it that a user sets a welcome channel and then welcome messages will send there?
Sorry, English is my second language.
You can simply set a variable (ex. welcomeChannel) equal to whatever the user wants their welcome channel to be and replace that with your 🚪welcome section.
Ex.
let welcomeChannel = "welcome" and then later on replace
const channel = member.guild.channels.cache.find(ch => ch.name === '🚪welcome');
with
const channel = member.guild.channels.cache.find(ch => ch.name === welcomeChannel);
Everything else will work just fine. If you wanted to create a command though to have the bot prompt it via messaging, that'll require a messageCollector. I'm not certain if that's what you're asking.

Bad performance compositing images with Sharp

I have the task of stacking several pngs one on top of another and exporting it as one png. The images do not have the same size, however they do not need to be resized, just cropped to the available canvas.
The images come from the file system and are sent as a buffer. There may be somewhere between 8 and 30 images.
My tests have been with 10 images, and I have compared it with node-canvas which takes less than 50% of the time that Sharp does. I think one problem with Sharp is that I have to make and extract operation before compositing.
This is my code running with Sharp, which takes ~600ms with 10 images:
export const renderComponentsSHARP = async ( layers )=>{
const images = [];
for(let i=0; i<layers.length; i++){
const layer = sharp(layers[i]));
const meta = await layer.metadata();
if(meta.height > AVATAR_SIZE.height || meta.width > AVATAR_SIZE.width)
layer.extract({ top:0, left:0, ...AVATAR_SIZE });
images.push({input:await layer.toBuffer()});
}
return sharp({
create: {
...AVATAR_SIZE,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 }
}})
.composite(images)
.png()
.toBuffer();
};
This is my code running with canvas, which takes ~280ms:
export const renderComponentsCANVAS = async ( layers )=>{
const canvas = createCanvas(AVATAR_SIZE.width, AVATAR_SIZE.height);
const ctx = canvas.getContext('2d');
const images = await Promise.all(layers.map( layer=>loadImage(layer)));
images.forEach(image=>{
ctx.drawImage(image, 0, 0);
});
return canvas.toBuffer();
};
Am I doing something wrong with sharp? Or is it not the tool for this job?

Node project exiting without error on Canvas.createCanvas()

I am making a Discord bot, and i want it to send a picture at a specific event (that is irrelevant). I'm facing a weird problem: whenever i try to use Canvas.createCanvas(x,y) my Node project exits without any error in logs (as im using it through batch, it just restarts and prints the 'ready' line). I think that the code is irrelevant here, because it just fails to do createCanvas.
I have tried adding console.log('line N') on each line with text, and from my testing it only comes to line 2. So line 3 (createCanvas) is not processed.
client.on('guildMemberAdd', async member => { //canvas IS defined earlier
const channel = 'channel id';
if (!channel) return;
const canvas = createCanvas(600, 600);
const ctx = canvas.getContext('2d');
const background = await loadImage('./images/welcome.png');
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#676767';
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(125, 125, 100, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
const { body: buffer } = await snekfetch.get(member.user.displayAvatarURL);
const avatar = await loadImage(buffer);
ctx.drawImage(avatar, 25, 25, 200, 200);
const attachment = new Discord.Attachment(canvas.toBuffer(), 'welcome-image.png');
channel.send(attachment);
});
I expect it to actually create the canvas and do the operations, but instead it just restarts the bot. Any help here?

Add image as frame to a gif

So I want to create a gif and add an image as a frame. I am using gifencoder. I've seen the examples and its pretty easy to add colors, txt etc, but I couldnt figure out how to do the same with an image. Do I need something like the png file stream.
ex: from website for color frame
let canvas = new Canvas(width, height);
var ctx = canvas.getContext('2d');
red rectangle
ctx.fillStyle = '#ff0000';
ctx.fillRect(0, 0, 320, 240);
encoder.addFrame(ctx);
Edit:
I tried doing something like below and while the frames appear on my log as wanted the frames dont get added, no errors. Before someone suggests to convert to base64 the png/gif this module cant add such frames, that being said if you have another in mind that does please go ahead and say so.
fs.createReadStream('test.gif')
.pipe(new GIFDecoder) //gif-stream
.pipe(concat(function(frames) { //concat-frames
console.log(frames);
for (var i=0; i<frames.length; i++) {
encoder.addFrame(frames[i]);
}
}));
whatever I have tried at best I get just a black gif, nothing more.
Edit 2:
Okay so the reason I am trying to add from from a gif the frame, was because it simply seemed easier. Below is the progress I have done trying to get an image, convert it to RGBA (i noticed the module accepts rgba format, not rgb, perhaps that's why it was always black) and afterwards add the frame. Adding the frame if I have the data is extremely easy as I simply call a method and push the data in, so I am going to leave that out.
var request = require('request').defaults({ encoding: null });
request.get('https://upload.wikimedia.org/wikipedia/commons/0/01/Quinlingpandabearr.jpg', function (error, response, body) {
if (!error && response.statusCode == 200) {
var img = new Canvas(105, 159);
var context = img.getContext('2d');
img.src = "data:" + response.headers["content-type"] + ";base64," + new Buffer(body).toString('base64');
var img_to_rgba = context.getImageData(0, 0, img.width, img.height);
console.log(img_to_rgba); //always returns 0, like my pic is completely black, its not.
}
});
I've added an image as a frame to a gif, along side a blue square.
Here is the full code, tested and working fine for me:
var GIFEncoder = require('gifencoder');
var Canvas = require('canvas');
var fs = require('fs');
var encoder = new GIFEncoder(320, 240);
encoder.createReadStream().pipe(fs.createWriteStream('myanimated.gif'));
encoder.start();
encoder.setRepeat(0);
encoder.setDelay(500);
encoder.setQuality(10);
var canvas = new Canvas(320, 240);
var ctx = canvas.getContext('2d');
// blue rectangle frame
ctx.fillStyle = '#0000ff';
ctx.fillRect(0, 0, 320, 240);
encoder.addFrame(ctx);
// image frame
var data = fs.readFileSync(__dirname + '/image.jpg');
var img = new Canvas.Image;
img.src = data;
ctx.drawImage(img, 0, 0, 320, 240);
encoder.addFrame(ctx);
encoder.finish();
Note that I'm using readFileSync this time, but you can use readFile too as in my other answer, depending on your code.
I've also change the size of the loaded image, to fit the square.
You can load the image onto the canvas with fs.readFile:
fs.readFile(__dirname + '/image.jpg', function(err, data) {
if (err) throw err;
var img = new canvas.Image;
img.src = data;
ctx.drawImage(img, 0, 0, img.width, img.height);
})

Check for transparency, GraphicsMagick node.js

I am making a code where users can upload a image. The image is converted with GraphicsMagick and uploaded to our cloud. But it will be best if non-transparent images was converted to JPG instead of PNG for transparent images. How can I check if the image contain a alpha channel in GraphicsMagick?
I am not sure you can achieve that using only GraphicsMagick, but it is possible in several other ways. For example with pngjs:
You can check PNG metadata:
const gm = require('gm');
const PNG = require('pngjs').PNG;
gm('/path/to/image')
.stream('png')
.pipe(new PNG({}))
.on('metadata', meta => {
if (meta.alpha) {
// image is transparent
} else {
// image is not transparent
}
});
Or iterate over pixels and decide if it's transparency valuable to you, or you can omit it:
...
.on('parsed', function() {
let isAlphaValuable = false;
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
// this.data[idx] - red channel
// this.data[idx + 1] - green channel
// this.data[idx + 2] - blue channel
// this.data[idx + 3] - alpha channel
// if there is at least one pixel
// which transparent for more than 30%
// then transparency valuable to us
isAlphaValuable |= (1 - this.data[idx + 3] / 255) > 0.3;
}
}
if (isAlphaValuable) {
// keep transparency
} else {
// ignore transparency
}
});
You may also give imagemagick a try
Snippet is in TypeScript and makes use of BPromise.promisify for better readability.
Note that this works for PNG, JPEG the expected way (returning string true/false), but for GIFs it will give you a concatenated 'true'|'false' string (e.g. 'truetruefalse', and apply the alpha check per frame).
I also recommend applying .trim() to the result to get rid of potential useless whitespace returned by imagemagick v0.x. every now and then.
import * as imagemagick from 'imagemagick';
import * as BPromise from 'bluebird';
...
const opaqueAsync: any = BPromise.promisify(imagemagick.identify, {context: imagemagick});
const isOpaqueReturnValue: string = await opaqueAsync(['-format', '%[opaque]', picturePath]);
const isPicTransparent: boolean = 'false' === isOpaqueReturnValue.trim();

Resources