I am serializing a canvas on the client, and post it to a node.js server (ubuntu 14.10, with node.js version v0.10.34 and fabric 1.4.13).
On the client canvas, objects are in a group.
The problem is, the objects are moved when de-serialized on the server.
Client code:
$(function(){
fc= new fabric.Canvas('myCanvas');
fc.setBackgroundColor('white');
group = new fabric.Group([], { hasControls:false, hasBorders:true, top:-fc.getHeight(), left:-fc.getWidth(), width:2*fc.getWidth(), height:2*fc.getHeight(), hoverCursor:'default' });
fc.add(group);
// create a rectangle object
var rect = new fabric.Rect({
left: 150,
top: 100,
fill: 'red',
width: 20,
height: 20
});
// "add" rectangle onto canvas
group.add(rect);
var rect2 = new fabric.Rect({
left: 100,
top: 150,
fill: 'blue',
width: 20,
height: 20
});
group.add(rect2);
fc.renderAll();
$.post( window.location.origin+':8124/', {
width: group.getWidth(),
height: group.getHeight(),
data: encodeURI(JSON.stringify(fc.toDatalessJSON()))
}, function( data ) {}
);
});
Server code:
var fabric = require('fabric').fabric;
var express = require('express');
var app = express();
var fs = require('fs');
var PORT = 8124;
var bodyParser = require('body-parser')
app.use(bodyParser.json({ limit: '50mb'}) ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true,
limit: '50mb'
}));
app.post('/', function(req, res){
console.log('Post received');
if (req.body) {
res.writeHead(200, { 'Content-Type': 'image/png' });
var w=parseInt(req.body.width);
var h=parseInt(req.body.height);
var canvas = fabric.createCanvasForNode(w, h);
console.log(req.body.data);
out = fs.createWriteStream(__dirname + '/mindmap.png');
canvas.loadFromDatalessJSON(decodeURI(req.body.data), function() {
canvas.renderAll();
console.log(JSON.stringify(canvas.toDatalessJSON()));
var stream = canvas.createPNGStream();
stream.on('data', function(chunk) {
out.write(chunk);
console.log('writing chunk');
});
stream.on('end', function() {
out.end();
});
});
}
});
app.listen(PORT);
The console.log statement shows that the two rects are created (left:15, top-35) and (left:-35, top:15) respectively.
On the client, top/left object properties are relative to center of the group.
This is why I create the group with -fc.getWidth and-fc.getHeight left and top respectively. This works fine on the client.
Maybe this is not the case on the server?
EDIT: this seems to be an issue with loadFromDatalessJSON, at least on node.
Running the following code on the node server shows that top/left properties of the rectangles are wrong after serializing the first canvas and deserializing into the second one:
var fabric = require('fabric').fabric;
var fs = require('fs');
var canvas = fabric.createCanvasForNode(200, 200);
canvas.setBackgroundColor('white');
var out = fs.createWriteStream(__dirname + '/mindmap.png');
var group = new fabric.Group([], { top:-200, left:-200, width:400, height:400});
canvas.add(group);
var rect = new fabric.Rect({
left:150,
top:100,
fill:'red',
width:20,
height:20
});
group.add(rect);
var rect2 = new fabric.Rect({
left:100,
top:150,
fill:'blue',
width:20,
height:20
});
group.add(rect2);
canvas.renderAll();
console.log(JSON.stringify(canvas.toDatalessJSON()));
var canvas2 = fabric.createCanvasForNode(200, 200);
canvas2.loadFromDatalessJSON(canvas.toDatalessJSON());
canvas2.renderAll();
var stream = canvas2.createPNGStream();
stream.on('data', function(chunk) {
out.write(chunk);
console.log('writing chunk');
});
stream.on('end', function() {
out.end();
console.log('png image generated');
});
console.log(JSON.stringify(canvas2.toDatalessJSON()));
Next step is to run similar code on the client and see if the problem exists as well.
EDIT2: the same problem occurs on the client, and with toJSON as well instead of toDatalessJSON. Can someone help? Is this a known issue with groups serialization/deserialization? Is there a workaround?
Thanks
After searching for similar issues, it looks like this is very similar to [#1159] [https://github.com/kangax/fabric.js/issues/1159]
But this issue is supposed to be fixed, and I don't have any transform on my canvas ...
I had this problem and fixed it by installing the latest version from the github releases section: https://github.com/kangax/fabric.js/releases
Related
I am using node canvas in a project; essentially the project is about displaying an image on the browser screen, and being able to control the position of tge image by way of REST API POSTs. This works fine, but upon a POST i want to refresh the browser automatically.
I have looked at:
npm packages (browser-refresh, etc) ; but they require to put some code in the client page, but i dont have any html client page.
calling res.redirect ; this doesnt seem to do anything.
calling my draw() method in the POST method: i m getting an error about 'write after end' .
any help ? Again, the ask is to refresh the browser (or part thereof) upon new coordinates, in the POST method.
Code below.
Cheers,
Matt
Node server.js:
//Lets require/import the HTTP module
var http = require('http');
var express = require('express');
var app = express();
var bodyParser = require('body-parser')
var fs = require('fs')
var path = require('path')
var draw = require('./draw_badge');
var robot1;
var robot1_xcoord = 30;
var robot1_ycoord = 100;
var robot2;
var robot2_xcoord = 50;
var robot2_ycoord = 30;
/** bodyParser.urlencoded(options)
* Parses the text as URL encoded data (which is how browsers tend to send form data from regular forms set to POST)
* and exposes the resulting object (containing the keys and values) on req.body
*/
app.use(bodyParser.urlencoded({
extended: true
}));
/**bodyParser.json(options)
* Parses the text as JSON and exposes the resulting object on req.body.
*/
app.use(bodyParser.json());
/** -------- Start -----
*
*/
{
app.get('/', function (req, res) {
console.log("Xcoord: " + robot1_xcoord);
res.setHeader('Content-Type', 'image/png');
// redraw everything
draw(robot1_xcoord,robot1_ycoord, robot2_xcoord,robot2_ycoord).pngStream().pipe(res);
});
// Getting a POST
app.post('/', function (req, res) {
console.log(req.body.id);
if (req.body.id=="1")
{
console.log("robot1 change");
robot1_xcoord = req.body.xcoordinate;
robot1_ycoord = req.body.ycoordinate;
}
else
if (req.body.id=="2")
{
console.log("robot2 change");
robot2_xcoord = req.body.xcoordinate;
robot2_ycoord = req.body.ycoordinate;
}
// draw(robot1_xcoord,robot1_ycoord, robot2_xcoord,robot2_ycoord).pngStream().pipe(res);
//res.redirect('localhost:5000');
res.send('Got a POST request' );
// try
//res.redirect(req.get('referer'));
/*
return http.get({
host: 'localhost',
path: '/'
}, function(response) {
// Continuously update stream with data
var body = '';
response.on('data', function(d) {
body += d;
});
response.on('end', function() {
// Data reception is done, do whatever with it!
var parsed = JSON.parse(body);
});
});
*/
});
// Main app - Listen
app.listen(process.env.PORT || 5000, function () {
console.log('Example app listening !');
});
and
draw_badge.js:
var Canvas = require('canvas')
var fs = require('fs')
function draw_badge(x,y) {
var x, y, i
ctx.clearRect(0, 0, 120, 120)
ctx.save()
ctx.translate(160, 160)
ctx.beginPath()
ctx.lineWidth = 14
ctx.strokeStyle = '#325FA2'
ctx.fillStyle = '#eeeeee'
ctx.arc(x, y, 42, 0, Math.PI * 2, true)
ctx.stroke()
ctx.fill()
return canvas;
}
function draw_robot(x,y) {
var Image = Canvas.Image
var canvas = new Canvas(600, 600)
var ctx = canvas.getContext('2d')
var img = new Image()
img.src = canvas.toBuffer()
ctx.drawImage(img, 0, 0, 50, 50)
ctx.drawImage(img, 50, 0, 50, 50)
ctx.drawImage(img, 100, 0, 50, 50)
img.src = fs.readFileSync('./kuka.png')
ctx.drawImage(img, 100, 0, img.width , img.height )
//img = new Image()
img.src = fs.readFileSync('./robot.jpeg')
ctx.drawImage(img, x, y, img.width / 2, img.height / 2)
// new
canvas.createPNGStream().pipe(fs.createWriteStream('./image-robot.png'))
return canvas
}
function draw(x1,y1,x2,y2)
{
Image = Canvas.Image,
canvas = new Canvas(600, 600),
ctx = canvas.getContext('2d');
canvas = draw_robot(x1,y1);
canvas = draw_badge(x2,y2);
return canvas;
}
module.exports = draw;
They way you are trying cannot work
The image that has been delivered to the browser cannot be refreshed dynamically, simply because it is an image. Once the server has delivered and the client loaded their work is done.
Attempting to write to the request (which may be one of hundreds) will of course result in a "write after end", because the end of the request was when the image first loaded in your browser.
the express res.redirect function cannot be called post facto (after the request) either, also it would immediately redirect, which you are not looking for.
Simple solution: Refresh via HTTP-Header (correctly)
app.get('/', function (req, res) {
console.log("Xcoord: " + robot1_xcoord);
res.setHeader('Content-Type', 'image/png');
// refresh every second
res.setHeader('Refresh','1');
// redraw everything
draw(robot1_xcoord,robot1_ycoord,robot2_xcoord,robot2_ycoord).pngStream().pipe(res);
});
Real solution: streaming image
You could supply an actual image-stream. The idea being that your request to the picture would never be closed, and when you alter the picture via your REST-API, the next picture of the stream would be delivered. In theory, your browser would display the last complete frame it got, thus kind of "update" your image in the browser window. This would be the real solution here, but might be expensive in terms of time wasted on the implementation. This would take some re-arrangements in your code.
caveat: firefox only, chrome support has been dropped as I just learned :/
server.js
//Lets require/import the HTTP module
var http = require('http');
var express = require('express');
var app = express();
var bodyParser = require('body-parser')
var fs = require('fs')
var path = require('path')
var draw = require('./draw_badge');
var robot1;
var robot1_xcoord = 30;
var robot1_ycoord = 100;
var robot2;
var robot2_xcoord = 50;
var robot2_ycoord = 30;
// An array to hold a list of active clients
var clients = [];
// draw an initial version of your buffer
var imageData = draw(robot1_xcoord, robot1_ycoord, robot2_xcoord, robot2_ycoord).toBuffer(undefined, 3, canvas.PNG_FILTER_NONE);
// get the size in bytes as well, we'll need it
var length = imageData.byteLength;
/** bodyParser.urlencoded(options)
* Parses the text as URL encoded data (which is how browsers tend to send form data from regular forms set to POST)
* and exposes the resulting object (containing the keys and values) on req.body
*/
app.use(bodyParser.urlencoded({
extended: true
}));
/**bodyParser.json(options)
* Parses the text as JSON and exposes the resulting object on req.body.
*/
app.use(bodyParser.json());
/** -------- Start -----
*
*/
app.get('/', function(req, res) {
// prepare header so that the browser will wait for arbitray updates
res.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace; boundary=--NEW_IMAGE_HERE',
'Cache-Control': 'no-cache',
'Connection': 'close',
'Pragma': 'no-cache'
});
var on_update = function(imageData, length) {
try {
console.log("Updating client.. bytes:", length)
res.write("--NEW_IMAGE_HERE\r\n");
res.write("Content-Type: image/png\r\n");
res.write("Content-Length: " + length + "\r\n\r\n");
res.write(imageData);
} catch (e) { // in case of an error remove from the clients array
console.log("Error: ", e);
clients.splice(clients.indexOf(on_update), 1);
}
}
// remove on disconnect
res.on('close', function() {
console.log("Disconnected");
clients.splice(clients.indexOf(on_update), 1);
});
// send the client our last cached version of the image
on_update(imageData, length);
// add our update function to the array of clients
clients.push(on_update);
});
// Getting a POST
app.post('/', function(req, res) {
console.log(req.body.id);
if (req.body.id == "1") {
console.log("robot1 change");
robot1_xcoord = req.body.xcoordinate;
robot1_ycoord = req.body.ycoordinate;
} else
if (req.body.id == "2") {
console.log("robot2 change");
robot2_xcoord = req.body.xcoordinate;
robot2_ycoord = req.body.ycoordinate;
}
res.send('Got a POST request');
// redraw everything into the buffer
imageData = draw(robot1_xcoord, robot1_ycoord, robot2_xcoord, robot2_ycoord).toBuffer(undefined, 3, canvas.PNG_FILTER_NONE);
length = imageData.byteLength;
// notify active clients
for (on_update of clients) {
on_update(imageData, length);
}
});
// Main app - Listen
app.listen(process.env.PORT || 5000, function() {
console.log('Example app listening !');
});
I have created gulp tasks scheduler for JavaScript minification and image resizing. All things work nicely when I executes command manually.
Command:
gulp
But when I include gulp-livereload module to perform operation automatically, Command line watch continuously when I upload images, it does not resize.
Just cursor is blinking. When uploads an image, no activities display in command watch list.
gulpfile.js
// include gulp
var gulp = require('gulp');
// include plug-ins
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var stripDebug = require('gulp-strip-debug');
var uglify = require('gulp-uglify');
var watch = require('gulp-watch');
var imageresize = require('gulp-image-resize');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant');
var liveReload = require("gulp-livereload");
// JS hint task
gulp.task('jshint', function () {
gulp.src([
'./app/client/app.js',
'./app/client/app.routes.js',
'./app/client/modules/**/controllers/*.js',
'./app/client/modules/**/directives/*.js',
'./app/client/modules/**/services/*.js',
'./app/client/services/*.js'])
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(liveReload());
});
gulp.task('jsminification', function () {
gulp.src([
'./app/client/app.js',
'./app/client/app.routes.js',
'./app/client/modules/**/controllers/*.js',
'./app/client/modules/**/directives/*.js',
'./app/client/modules/**/services/*.js',
'./app/client/modules/**/filter/*.js',
'./app/client/services/*.js'])
.pipe(concat('script.js'))
.pipe(stripDebug())
.pipe(uglify())
.pipe(gulp.dest('./app/build/scripts/'))
.pipe(liveReload());
});
gulp.task('resize', function () {
// set the folder name and the relative paths
// in the example the images are in ./assets/images
// and the public directory is ../public
var paths = {
folder: 'media/',
src: './app/client/',
dest: './app/client/resize/'
};
// create an array of image groups (see comments above)
// specifying the folder name, the ouput dimensions and
// whether or not to crop the images
var images = [
// {folder: 'bg', width: 1200, crop: false},
{folder: 'photo', width: 120, height: 120, crop: true},
//{folder: 'projects', width: 800, height: 500, crop: true}
];
console.log("resize called");
// loop through image groups
images.forEach(function (type) {
console.log(type);
var source_ = paths.src + paths.folder + type.folder + '/*';
var scale_ = type.width + "x" + type.height + "/";
//var destination_ = paths.dest + paths.folder + scale_ + type.folder;
var destination_ = paths.dest + scale_ + type.folder;
console.log(">source:" + source_);
console.log(">scale:" + scale_);
console.log(">destination:" + destination_);
// build the resize object
var resize_settings = {
width: type.width,
crop: type.crop,
// never increase image dimensions
upscale: false
}
// only specify the height if it exists
if (type.hasOwnProperty("height")) {
resize_settings.height = type.height;
}
gulp
// grab all images from the folder
.src(source_)
// resize them according to the width/height settings
.pipe(imageresize(resize_settings))
// output each image to the dest path
// maintaining the folder structure
.pipe(gulp.dest(destination_))
.pipe(liveReload());
});
});
gulp.task('watch', function () {
liveReload.listen({host: process.env['HOST'], port: process.env['PORT']});
gulp.watch('./app/client/media/photo/*.{png,jpg,jpeg}', ['resize']);
});
gulp.task('default', ['jshint', 'jsminification', 'resize', 'watch']);
I want to resize image automatically as new image uploads in photo folder.
After some R&D I have found following solution using "gulp-watch" module and it works fine.
// include gulp
var gulp = require('gulp');
// include plug-ins
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var stripDebug = require('gulp-strip-debug');
var uglify = require('gulp-uglify');
var imageresize = require('gulp-image-resize');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant');
var watch = require("gulp-watch");
var newer = require("gulp-newer");
var paths = {
folder: 'media/',
src: './app/client/',
dest: './app/client/resize/'
}
var images = [
{folder: 'photo', width: 120, height: 120, crop: true},
];
// JS hint task
gulp.task('jshint', function () {
gulp.src([
'./app/client/app.js',
'./app/client/app.routes.js',
'./app/client/modules/**/controllers/*.js',
'./app/client/modules/**/directives/*.js',
'./app/client/modules/**/services/*.js',
'./app/client/services/*.js'])
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
// JS minification task
gulp.task('jsminification', function () {
gulp.src([
'./app/client/app.js',
'./app/client/app.routes.js',
'./app/client/modules/**/controllers/*.js',
'./app/client/modules/**/directives/*.js',
'./app/client/modules/**/services/*.js',
'./app/client/modules/**/filter/*.js',
'./app/client/services/*.js'])
.pipe(concat('script.js'))
.pipe(stripDebug())
.pipe(uglify())
.pipe(gulp.dest('./app/build/scripts/'));
});
// image resize
gulp.task('resize', function () {
// loop through image groups
images.forEach(function (type) {
var source_ = paths.src + paths.folder + type.folder + '/*';
var scale_ = type.width + "x" + type.height + "/";
//var destination_ = paths.dest + paths.folder + scale_ + type.folder;
var destination_ = paths.dest + scale_ + type.folder;
// build the resize object
var resize_settings = {
width: type.width,
crop: type.crop,
// never increase image dimensions
upscale: false
}
// only specify the height if it exists
if (type.hasOwnProperty("height")) {
resize_settings.height = type.height;
}
gulp
// grab all images from the folder
.src(source_)
.pipe(newer(destination_))
// resize them according to the width/height settings
.pipe(imageresize(resize_settings))
// optimize the images
.pipe(imagemin({
progressive: true,
// set this if you are using svg images
svgoPlugins: [{removeViewBox: false}],
use: [pngquant()]
}))
// output each image to the dest path
// maintaining the folder structure
.pipe(gulp.dest(destination_));
});
});
// Gulp default task
gulp.task('default', ['jshint', 'jsminification', 'resize'], function () {});
// Gulp watch for new image resizing
watch('./app/client/media/photo/*.+(png|jpg|jpeg|gif)', function () {
gulp.run('resize');
});
I'm using the d3 library. Its pretty big, so I only want to download the chunk on demand. I can create the split point with require.ensure, but I'm lost as to how you actually get the library into the scope of the module. This is as close as I can get, the chunk is downloaded, but d3 is always undefined. Any help would be greatly appreciated.
var React = require('react');
var d3;
require.ensure([], function(require){
d3 = require('d3');
});
var Chart = React.createClass({
render: function() {
var props = this.props;
var width = props.width;
//d3 is undefined here
var xScale = d3.scale.linear().domain([
0, 111
]).range([
0, width
]);
return (
<g>
</g>
);
}
});
module.exports = Chart;
From my understanding the mechanism goes like this:
require.ensure(["<file-path or module name>"], function(moduleExportsValue) {
var myModule = require("<file-path or module name>");
});
for your specific example:
require.ensure(["d3"], function() {
var d3 = require("d3");
// do something with d3
});
Edit: It's very helpful for stuff that is not immediately visible in the app. Such as dialogs, modals, overlays.
onButtonClick: function(event) {
this.showLoader();
var self = this;
require.ensure("path/to/dialog", function() {
self.hideLoader();
var dialog = require("path/to/dialog");
dialog.open();
});
},
This is basic imagemagick setup for node.js that i came across lately. I understand that question i'm asking is very newbie but i'm new to node js and imagemagick and i wanted to try to make it dynamic. Current script bellow is cropping one specific file, is there a way to make it to apply conversion to all the files from directory /before and then output it to directory /after ? Also the operation i want to do for all the images in directory is to apply watermark on each (crop operation in code is just from example).
var http = require("http");
var im = require("imagemagick");
var args = [
"image.png",
"-crop",
"120x80+30+15",
"output.png"
];
var server = http.createServer(function(req, res) {
im.convert(args, function(err) {
if(err) { throw err; }
res.end("Image crop complete");
});
}).listen(8080);
Yes, you can do it by implementing foreach by all files itself.
Also you will need to install async module:
npm install async
Example code:
var async = require('async'),
fs = require('fs'),
im = require('imagemagick'),
maxworkers = require('os').cpus().length,
path = require('path');
module.exports = resize;
function resize(params) {
var queue = async.queue(resizeimg, maxworkers);
fs.readdir(params.src, function(err, files) {
files.forEach(function(file) {
queue.push({
src: path.join(params.src, '/', file),
dest: path.join(params.dest, '/', file),
width: params.width,
height: params.height
})
});
});
}
function resizeimg(params, cb) {
var imoptions = {
srcPath: params.src,
dstPath: params.dest
};
if (params.width !== undefined) imoptions.width = params.width;
if (params.height !== undefined) imoptions.height = params.height
im.resize(imoptions, cb);
}
Then in you code:
var http = require("http");
var resize = require("./resize");
var server = http.createServer(function(req, res) {
resize({
src: '/source/folder',
dest: '/destination/folder',
width: 300
});
}).listen(8080);
I'm a beginner on node.js and socket.io. Socket.io began to support binary stream from 1.0, is there a complete example especially for image push to client and show in canvas? thanks
The solution is a bit complicated but should work in Chrome, Firefox, and IE10+ (not sure about Opera and Safari):
Somewhere on the server-side:
io.on('connection', function(socket){
fs.readFile('/path/to/image.png', function(err, buffer){
socket.emit('image', { buffer: buffer });
});
});
And here is how you handle it on a client:
socket.on('image', function(data) {
var uint8Arr = new Uint8Array(data.buffer);
var binary = '';
for (var i = 0; i < uint8Arr.length; i++) {
binary += String.fromCharCode(uint8Arr[i]);
}
var base64String = window.btoa(binary);
var img = new Image();
img.onload = function() {
var canvas = document.getElementById('yourCanvasId');
var ctx = canvas.getContext('2d');
var x = 0, y = 0;
ctx.drawImage(this, x, y);
}
img.src = 'data:image/png;base64,' + base64String;
});
Just replace yourCanvasId with your canvas id :)
thanks, #sovente, in this 1.0 introduction http://socket.io/blog/introducing-socket-io-1-0/ , this is code snippet on binary support.
var fs = require('fs');
var io = require('socket.io')(3000);
io.on('connection', function(socket){
fs.readFile('image.png', function(err, buf){
// it's possible to embed binary data
// within arbitrarily-complex objects
socket.emit('image', { image: true, buffer: buf });
});
});
i want to know how to handle the buffer on client side, codes are like:
socket.on("image", function(image, buffer) {
if(image)
{
console.log(" image: ");
**// code to handle buffer like drawing with canvas**
}
});
Starting from socket.io 1.0 it is possible to send binary data.
http://socket.io/blog/introducing-socket-io-1-0/
How ever the way of sending and receiving binary data is not clear in the official documentation. The only documentation is:
var socket = new WebSocket('ws://localhost');
socket.binaryType = 'arraybuffer';
socket.send(new ArrayBuffer);
I suggest you to take a look at this answer, where you can find code implementation for server and client (javascript, java):
https://stackoverflow.com/questions/34056705/how-to-send-binary-data-with-socket-io/
The good part is that it also works on Android!
Cheers