Fabric.js loadFromJSON sometimes fails in Node.js if string contains images - node.js
I have a problem with PNG image ganeration at server side, using Fabric.js + Node.js. I am wondering that there is no one with similar probem found in forums. I am in total despair. It makes under risk of using Fabric.js in our project.
PNG image generation in Fabric.js Node.js service fails on a unregular basis. I can not determine why sometimes it gets generated and sometimes not.
I need to generate PNG at server side. I’ve developed a small Node.js webservice based on samples here and here.
I’ve developed also a custom Fabric.js image class “RemoteImage”, based on Kangax sample here.
To minimize JSON string size, I am storing a dataless JSON in my database and images are supposed to be loaded using provide link in “src” attribute of the Fabric.js Image element. As the result, I need to load following JSON into canvas that contains 3 images:
{"objects":[{"type":"remote-image","originX":"left","originY":"top","left":44,"top":29,"width":976,"height":544,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":0.5,"scaleY":0.5,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","localId":"222c0a8b-46ac-4c01-9c5c-79753937bc24","layerName":"productCanvas","itemName":"mainCanvas","src":"http://localhost:41075/en/RemoteStorage/GetRemoteItemImage/222c0a8b-46ac-4c01-9c5c-79753937bc24","filters":[],"crossOrigin":"use-credentials","alignX":"none","alignY":"none","meetOrSlice":"meet","remoteSrc":"http://localhost:41075/en/RemoteStorage/GetRemoteItemImage/222c0a8b-46ac-4c01-9c5c-79753937bc24","lockUniScaling":true},
{"type":"remote-image","originX":"left","originY":"top","left":382.5,"top":152.25,"width":292,"height":291,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":0.43,"scaleY":0.43,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","localId":"8d97050e-eae8-4e95-b50b-f934f0df2d4c","itemName":"BestDeal.png","src":"http://localhost:41075/en/RemoteStorage/GetRemoteItemImage/8d97050e-eae8-4e95-b50b-f934f0df2d4c","filters":[],"crossOrigin":"use-credentials","alignX":"none","alignY":"none","meetOrSlice":"meet","remoteSrc":"http://localhost:41075/en/RemoteStorage/GetRemoteItemImage/8d97050e-eae8-4e95-b50b-f934f0df2d4c","lockUniScaling":true},
{"type":"remote-image","originX":"left","originY":"top","left":38,"top":38.5,"width":678,"height":370,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":0.21,"scaleY":0.21,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","localId":"42dc0e49-e45f-4aa7-80cf-72d362deebb7","itemName":"simple_car.png","src":"http://localhost:41075/en/RemoteStorage/GetRemoteItemImage/42dc0e49-e45f-4aa7-80cf-72d362deebb7","filters":[],"crossOrigin":"use-credentials","alignX":"none","alignY":"none","meetOrSlice":"meet","remoteSrc":"http://localhost:41075/en/RemoteStorage/GetRemoteItemImage/42dc0e49-e45f-4aa7-80cf-72d362deebb7","lockUniScaling":true}],"background":""}
At Node.js server side I use the following code. I am transferring JSON string in base64 encoding to avoid some special-character problems:
var fabric = require('fabric').fabric;
function generatePNG(response, postData) {
var canvas = fabric.createCanvasForNode(1500, 800);
var decodedData = new Buffer(postData, 'base64').toString('utf8');
response.writeHead(200, "OK", { 'Content-Type': 'image/png' });
console.log("decodedData data: " + JSON.stringify(decodedData));
console.log("prepare to load");
canvas.loadFromJSON(decodedData, function () {
console.log("loaded");
canvas.renderAll();
console.log("rendered");
var stream = canvas.createPNGStream();
stream.on('data', function (chunk) {
response.write(chunk);
});
stream.on('end', function () {
response.end();
});
});
}
In a console I see that message “prepare to load” appears, but message “loaded” does not. I am not an expert in Node.js and this is the only way how I can determine that error happens during the loadFromJSON call. But I do not understand, where is the problem.
I am using fabric v.1.5.0 and node-canvas v.1.1.6 on server side.
Node.js + Fabric.js service is running on Windows 8 machine. And I am makeing a request from .NET MVC application, using POST request.
Remark: May be I needed to omit my comment about base64 encoding as it is confusing. I tried to run with normal json string and the same result.
If the images referenced in the JSON are on the NodeJS server, try changing the file path to the directory path on the server as opposed to a web URL.
I'm not sure I fully understand how you are using the base64 image, but there are some character corrections that are required for base64 images. I of course don't recall the specifics and don't have my code handy that I perform this in, but a Google search should set you in the right direction.
I hope those ideas help.
It turned out that problem was related to the way how fabric.util.loadImage method works. For external images loadImage mathod makes an http request assuming that no error can happen. Method used for requesting external images just simply logs an error and ends, instead of returning error through callback method back to loadImage method. At this moment image loading routine falls apart with erroneous state and without any feedback - it just terminates crashing whole Node.js.
It took 3 days for me to finally find out that actually it was my image supplying webservice who just responds with status code 500 making Node.js request to fail. Using my image supplying webservice through browser worked correctly and therefore at the first moment I did not considered that error is related particularly with request.
As the result I rewrote fromObject method of my custom Fabric.js object. Now it works in more safe fashion and in case of error I can get more feedback. Here is the implementation of my fromObject method. For http request I use module "request".
fabric.RemoteImage.fromObject = function (object, callback) {
var requestUrl = object.remoteSrc;
request({
url: object.remoteSrc,
encoding: null
},
function(error, response, body) {
if (error || response.statusCode !== 200) {
var errorMessage = "Error retrieving image " + requestUrl;
errorMessage += "\nResponse for a new image returned status code " + response.statusCode;
if (error) {
errorMessage += " " + error.name + " with message: \n" + error.message;
console.log(error.stack);
}
console.log(errorMessage);
callback && callback(null, new Error(errorMessage));
} else {
var img = new Image();
var buff = new Buffer(body, 'binary');
img.src = buff;
var fabrImg = new fabric.RemoteImage(img, object);
callback && callback(fabrImg);
}
});
};
Related
How to download a multipart wav file from cloudant database and save locally using Node JS and REST API?
I am stuck in retrieving multipart from cloudant using Node JS API. Hence, I used REST API to download the wav file from cloudant database. But its not downloading wav file from https URL. When I enter the https URL directly in browser, it prompts me to save file locally. So, the URL is correct. Here is the code for REST API: var request1 = require('request'); var filestream = fs.createWriteStream("input.wav"); var authenticationHeader = "Basic " + new Buffer(user + ":" + pass).toString("base64"); request1( { url : "example.com/data/1533979044129/female";, headers : { "Authorization" : authenticationHeader } }, function (error, httpResponse, body) { const statusCode = httpResponse.statusCode; httpResponse.pipe(filestream); httpResponse.on('end', function () { console.log("file complete"); filestream.close(); }); }); The file size of input.wav is 0. Its not downloading file. Please help.
Your callback has an error argument, which you are completely ignoring. Do something with this error, like print it out so your problem can tell you what you're doing wrong. I definitely see at least 1 problem in your source, and the error from request should tell you what it is. Edit On second thought the above code shouldn't even execute. You should share code that you tested yourself. There's typos in there.
NodeJS/React: Taking a picture, then sending it to mongoDB for storage and later display
I've been working on a small twitter-like website to teach myself React. It's going fairly well, and i want to allow users to take photos and attach it to their posts. I found a library called React-Camera that seems to do what i want it do to - it brings up the camera and manages to save something. I say something because i am very confused about what to actually -do- with what i save. This is the client-side code for the image capturing, which i basically just copied from the documentation: takePicture() { try { this.camera.capture() .then(blob => { this.setState({ show_camera: "none", image: URL.createObjectURL(blob) }) console.log(this.state); this.img.src = URL.createObjectURL(blob); this.img.onload = () => { URL.revokeObjectURL(this.src); } var details = { 'img': this.img.src, }; var formBody = []; for (var property in details) { var encodedKey = encodeURIComponent(property); var encodedValue = encodeURIComponent(details[property]); formBody.push(encodedKey + "=" + encodedValue); } formBody = formBody.join("&"); fetch('/newimage', { method: 'post', headers: {'Content-type': 'application/x-www-form-urlencoded;charset=UTF-8'}, body: formBody }); console.log("Reqd post") But what am i actually saving here? For testing i tried adding an image to the site and setting src={this.state.img} but that doesn't work. I can store this blob (which looks like, for example, blob:http://localhost:4000/dacf7a61-f8a7-484f-adf3-d28d369ae8db) or the image itself into my DB, but again the problem is im not sure what the correct way to go about this is. Basically, what i want to do is this: 1. Grab a picture using React-Camera 2. Send this in a post to /newimage 3. The image will then - in some form - be stored in the database 4. Later, a client may request an image that will be part of a post (ie. a tweet can have an image). This will then display the image on the website. Any help would be greatly appreciated as i feel i am just getting more confused the more libraries i look at!
From your question i came to know that you are storing image in DB itself. If my understanding is correct then you are attempting a bad approcah. For this you need to store images in project directory using your node application. need to store path of images in DB. using these path you can fetch the images and can display on webpage. for uploading image using nodejs you can use Multer package.
Error when downloading a file with Meteor from Github assets API
I'm stuck with a bug in my Meteor application. What I'm trying to do is to get a release asset file from github, and unzip it. I was able to download it from a standard browser. The result from my Meteor request contain a Buffer, that I'm able to save to a binary if I want to, but is different from the binary I got from my browser ( I compared the hex code for each files, and even the size is different). When I'm trying to open the archive file downloaded through Meteor (with windows zip program or with JSZip) It tells me that the file is corrupted. Here is the code I used to download the file : HTTP.call('GET',asset.url,{ // asset.url is a valid one params:{ 'access_token':token }, headers: { 'Accept':"application/octet-stream", 'User-Agent':"My app", } },function( error, result ) { if(error)console.log(error); else{ console.log('file downloaded !'); var app_archive = new JSZip(); // I'm using JSZip for decompressing the stream app_archive.load(new Buffer(result)); // fail here package_file = app_archive.file('package.json'); console.log(package_file); } }); and here is the Meteor console output : => Meteor server restarted I20160313-16:56:43.975(-5)? file created ! I20160313-16:56:44.105(-5)? Exception in callback of async function: Error: Corr upted zip : can't find end of central directory I20160313-16:56:44.106(-5)? at Object.ZipEntries.readEndOfCentral (C:\Users\ jimmy\AppData\Local\.meteor\packages\udondan_jszip\2.4.0_1\npm\node_modules\jszi p\lib\zipEntries.js:135:19) I20160313-16:56:44.108(-5)? at Object.ZipEntries.load (C:\Users\jimmy\AppDat a\Local\.meteor\packages\udondan_jszip\2.4.0_1\npm\node_modules\jszip\lib\zipEnt ries.js:197:14) I20160313-16:56:44.114(-5)? at Object.ZipEntries (C:\Users\jimmy\AppData\Loc al\.meteor\packages\udondan_jszip\2.4.0_1\npm\node_modules\jszip\lib\zipEntries. js:21:14) I20160313-16:56:44.116(-5)? at Object.module.exports [as load] (C:\Users\jim my\AppData\Local\.meteor\packages\udondan_jszip\2.4.0_1\npm\node_modules\jszip\l ib\load.js:11:18) I20160313-16:56:44.117(-5)? at server/FabMo-App-Store.js:122:19 I20160313-16:56:44.119(-5)? at runWithEnvironment (packages/meteor/dynamics_ nodejs.js:110:1) I think It may be related to an encoding issue, but I tried almost every encoding format without any success. I'm open to any suggestion.
You're right, this is an encoding issue. From the documentation, you get in the result the body of the HTTP response as a string. To get the content as string, you/the browser/the framework will need to decode it from its binary form using its encoding (UTF8 usually). You try to get a binary file, "decoding" it will corrupt it. You need to get the result in a binary format. The issue #1670 looked promising but wasn't merged. Using meteor add http aldeed:http, I get HTTP.call('GET',asset.url,{ params:{ responseType: "arraybuffer" // ... }, // ... },function( error, result ) { var app_archive = new JSZip(); app_archive.load(result); // result is an ArrayBuffer });
Finally made it working by using the request package Here is the code : request({ method : "GET", url : asset.url, headers:{ 'Accept':"application/octet-stream", 'User-Agent':"My App", 'token':token }, encoding: null // <- this one is important ! }, function (error, response, body) { if(error || response.statusCode !== 200) { // handle error } var app_archive = new JSZip(); app_archive.load(body); package_file = app_archive.file('package.json').asText(); console.log(package_file); });
check on server side if youtube video exist
How to check if youtube video exists on node.js app server side: var youtubeId = "adase268_"; // pseudo code youtubeVideoExist = function (youtubeId){ return true; // if youtube video exists }
You don't need to use the youtube API per-se, you can look for the thumbnail image: Valid video = 200 - OK: http://img.youtube.com/vi/gC4j-V585Ug/0.jpg Invalid video = 404 - Not found: http://img.youtube.com/vi/gC4j-V58xxx/0.jpg I thought I could make this work from the browser since you can load images from a third-party site without security problems. But testing it, it's failing to report the 404 as an error, probably because the content body is still a valid image. Since you're using node, you should be able to look at the HTTP response code directly.
I can't think of an approach that doesn't involve making a separate HTTP request to the video link to see if it exists or not unless you know beforehand of a set of video IDs that are inactive,dead, or wrong. Here's an example of something that might work for you. I can't readily tell if you're using this as a standalone script or as part of a web server. The example below assumes the latter, assuming you call a web server on /video?123videoId and have it respond or do something depending on whether or not the video with that ID exists. It uses Node's request library, which you can install with npm install request: var request = require('request'); // Your route here. Example on what route may look like if called on /video?id=123videoId app.get('/video', function(req, response, callback){ var videoId = 'adase268_'; // Could change to something like request.params['id'] request.get('https://www.youtube.com/watch?v='+videoId, function(error, response, body){ if(response.statusCode === 404){ // Video doesn't exist. Do what you need to do here. } else{ // Video exists. // Can handle other HTTP response codes here if you like. } }); }); // You could refactor the above to take out the 'request.get()', wrap it in a function // that takes a callback and re-use in multiple routes, depending on your problem.
#rodrigomartell is on the right track, in that your check function will need to make an HTTP call; however, just checking the youtube.com URL won't work in most cases. You'll get back a 404 if the videoID is a malformed ID (i.e. less than 11 characters or using characters not valid in their scheme), but if it's a properly formed videoID that just happens to not correspond to a video, you'll still get back a 200. It would be better to use an API request, like this (note that it might be easier to use the request-json library instead of just the request library): request = require('request-json'); var client = request.newClient('https://www.googleapis.com/youtube/v3/'); youtubeVideoExist = function (youtubeId){ var apikey ='YOUR_API_KEY'; // register for a javascript API key at the Google Developer's Console ... https://console.developers.google.com/ client.get('videos/?part=id&id='+youtubeId+'&key='+apikey, function(err, res, body) { if (body.items.length) { return true; // if youtube video exists } else { return false; } }); };
Using youtube-feeds module. Works fast (~200ms) and no need API_KEY youtube = require("youtube-feeds"); existsFunc = function(youtubeId, callback) { youtube.video(youtubeId, function(err, result) { var exists; exists = result.id === youtubeId; console.log("youtubeId"); console.log(youtubeId); console.log("exists"); console.log(exists); callback (exists); }); }; var notExistentYoutubeId = "y0srjasdkfjcKC4eY" existsFunc (notExistentYoutubeId, console.log) var existentYoutubeId = "y0srjcKC4eY" existsFunc (existentYoutubeId, console.log) output: ❯ node /pathToFileWithCodeAbove/FileWithCodeAbove.js youtubeId y0srjcKC4eY exists true true youtubeId y0srjasdkfjcKC4eY exists false false
All you need is to look for the thumbnail image. In NodeJS it would be something like var http = require('http'); function isValidYoutubeID(youtubeID) { var options = { method: 'HEAD', host: 'img.youtube.com', path: '/vi/' + youtubeID + '/0.jpg' }; var req = http.request(options, function(res) { if (res.statusCode == 200){ console.log("Valid Youtube ID"); } else { console.log("Invalid Youtube ID"); } }); req.end(); } API_KEY is not needed. It is quite fast because there is only header check for statusCode 200/404 and image is not loaded.
Serving out saved Buffer from Mongo
I'm trying to serve out images that I have stored in a Mongo document. I'm using express, express-resource and mongoose. The data, which is a JPG, is stored in a Buffer field in my schema. Seems like it's getting there correctly as I can read the data using the cli. Then I run a find, grab the field and attempt sending it. See code: res.contentType('jpg'); res.send(img); I don't think it's a storage issue because I'm performing the same action here: var img = fs.readFileSync( __dirname + '/../../img/small.jpg' ); res.contentType('jpg'); res.send(img); In the browser the image appears (as a broken icon). I'm wondering if it's an issue with express-resource because I have the format set to json, however I am indeed overriding the content type before sending the data. scratches head
I managed to solve this myself. Seems like I was using the right method to send the data from express, but wasn't storing it properly (tricky!). For future reference to anyone handling image downloads and managing them in Buffers, here is some sample code using the request package: request( { uri: uri, encoding: 'binary' }, function (err, response, body) { if (! err && response.statusCode == 200) { var imgData = new Buffer( body.toString(), 'binary' ).toString('base64'); callback(null, new Buffer(imgData, 'base64')); } } ); Within Mongo you need to setup a document property with type Buffer to successfully store it. Seems like this issue was due to how I was saving it into Mongo. Hopefully that saves someone time in the future. =)