I am using sailsjs, the website is sailsjs.org.
Im tring to create a controller function so that when an image is requested it can be resized and cached the first time a visitor asks for an image, then retrieved from cache the next time a visitor requests it.
I have setup a route to an asset controller:
'get /image/:width/:height/:image': {
controller: 'AssetController',
action: 'image'
},
I have setup a policy to intercept requests for an image from the above route and resize / save the file into a cache:
var fs = require('fs'),
im = require('imagemagick');
module.exports = function imageCacheResize(req, res, next){
var width = req.param('width');
var height = req.param('height');
var file = req.param('image');
if(width && height){
//read from cache
dir = 'assets/images/uploads/cache/'+width+'X'+height+'/';
filename = dir+file;
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
if(!fs.exists(filename)){
//write to file if not exist
var originalFile = 'assets/images/uploads/'+file;
im.resize({
srcPath: originalFile,
dstPath: filename,
width: width
}, function(err, stdout, stderr){
if (err) throw err;
});
}
}
next();
};
I have also got an action/function setup for the controller to handle returning the resized image:
image: function(req, res){
var file = req.param('image');
var filename = 'assets/images/uploads/'+file;
if((typeof req.param('width') != 'undefined')
&&(typeof req.param('height') != 'undefined'))
{
var width = req.param('width');
var height = req.param('height');
}
if(typeof req.param('size') != 'undefined'){
var size = req.param('size');
}
if(width && height){
//read from cache
dir = 'assets/images/uploads/cache/'+width+'X'+height+'/';
file = dir+file;
}else{
file = 'assets/images/uploads/'+file;
}
console.log(file);
res.sendfile(file);
}
All of the code works correctly and creates then saves the resized image, however it does not return the image on the first request but it does on the second.
So it turns out I had not placed the next() commands in the correct place. Seems like whilst waiting for an image resize the code continues and hits the final next().
I have change the image resize policy to this:
var fs = require('fs'),
im = require('imagemagick');
module.exports = function imageCacheResize(req, res, next){
var width = req.param('width');
var height = req.param('height');
var file = req.param('image');
if(width && height){
//read from cache
dir = 'assets/images/uploads/cache/'+width+'X'+height+'/';
filename = dir+file;
if (!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
if(!fs.exists(filename)){
//write to file if not exist
var originalFile = 'assets/images/uploads/'+file;
im.resize({
srcPath: originalFile,
dstPath: filename,
width: width
}, function(err, stdout, stderr){
if (err) throw err;
next();
});
}else{
next();
}
}else{
next();
}
};
Related
I use this code to upload images in Node:
req.file('image[]')
.upload({
maxBytes: 5000000, // Files limit(in bytes)
dirname: path.resolve(sails.config.appPath, 'assets/images/user') // Path to copy the files
}, function whenDone(err, uploadedFiles) {
if (err) {
// here must delete the created user before the error
console.log(err);
}
var image_real_name = '';
var json = [];
for(var i = 0; i < uploadedFiles.length; i++){
image_real_name = 'images/user/'+path.basename(uploadedFiles[i].fd);
json.push(image_real_name);
}
res(json);
});
but I needed to compress the images to gain space on the server so I used the gm GraphicsMagick:
`
var receiver = new Writable({objectMode: true});
receiver._write = function(file, enc, cb) {
// The output stream to pipe to
var output = require('fs').createWriteStream('assets/images/user/' + file.fd);
gm(file).resize('200', '200').stream().pipe(output);
cb();
};
req.file('image[]').upload(receiver, function(err, files){
if (err) {
// here must delete the created user before the error
console.log(err);
}
var image_real_name = '';
var json = [];
for(var i = 0; i < files.length; i++){
image_real_name = 'images/user/'+path.basename(files[i].fd);
json.push(image_real_name);
}
res(json);
});
`
I am using angular and when I change the direction of the image by binding the new image should be shown but it is not loaded, with the first code all perfect but using gm does not work anymore. I have to restart the page so that the image is displayed.
track:
apparently the image has not been fully processed, if a setTime is entered, the image is displayed correctly:
setTimeout((dataService: DataService, image: any) => {
dataService.getUser().image = image;
}, 1000, this.data, dataI.json().url);
any idea?
There is a finish event when the file is fully loaded, I solved the problem by adding the following:
output.on("finish", function() { return cb(); });
So here is how my app handle image uploading and generate thumbnail
router.post('/upload', saveupload, generateThumb, saveDB)
function generateThumb (req, res, next){
var filePath = req.files.file.path;
var fileDir = req.files.file.dir;
var filename = req.files.file.name;
async.series({
noProfile:function(callback){
gm(filePath)
.noProfile()
.write(filePath, function(err){
callback(err);
})
},
large:function(callback){
var thumb_width = config.thumb.lg.width;
var thumb_height = config.thumb.lg.height;
var quality = config.thumb.lg.quality;
var thumbName = filename.split('.')[0]+'_thumb_'+thumb_width+'x'+thumb_height+'.'+filename.split('.')[1];
var thumbPath = path.join(fileDir, thumbName);
gm(filePath)
.noProfile()
.resize(thumb_width, thumb_height)
.quality(quality)
.write(thumbPath, function(err){
callback(err)
})
},
middle:function(callback){
var thumb_width = config.thumb.md.width;
var thumb_height = config.thumb.md.height;
var quality = config.thumb.md.quality;
var thumbName = filename.split('.')[0]+'_thumb_'+thumb_width+'x'+thumb_height+'.'+filename.split('.')[1];
var thumbPath = path.join(fileDir, thumbName);
gm(filePath)
.noProfile()
.resize(thumb_width, thumb_height)
.quality(quality)
.write(thumbPath, function(err){
callback(err)
})
},
small:function(callback){
var thumb_width = config.thumb.sm.width;
var thumb_height = config.thumb.sm.height;
var quality = config.thumb.sm.quality;
var thumbName = filename.split('.')[0]+'_thumb_'+thumb_width+'x'+thumb_height+'.'+filename.split('.')[1];
var thumbPath = path.join(fileDir, thumbName);
gm(filePath)
.noProfile()
.resize(thumb_width, thumb_height)
.quality(quality)
.write(thumbPath, function(err){
callback(err)
})
}},
function(err, obj){
if(err){
return next(err)
}
next()
})
}
the code work well, util now I find some problem:
1:generate thumbnail this way slows the uploading speed
for every picture user uploaded, I need to generate 3 thumbnails , large, middle, small, I first save the uploaded picture to disk then pick up the picture in gm to crop metadata and generate thumbnail, this method really slow down the uploading speed, new upload can not start util the previous thumbnail is generated. what I want is keep the thumbnail generation out of the request route, so I can maintain max upload speed. any idea how?
2:Is there a better way to batch generate thumbnail
As you can see, I need 3 thumbnail, I did not see any document of gm on how to batch generate thumbnail, but I think there has to be a better way.
I am using loopback for storing Image to the server.
I want to modify the file name of the file before getting saved to the server.
Also I want to convert it to another thumbnail form before getting saved.
Here is how I am doing.
At client side
Upload.upload(
{
url: '/api/containers/container_name/upload',
file: file,
fileName: "demoImage.jpg",
//Additional data with file
params:{
orderId: 1,
customerId: 1
}
});
At Server Side I am receiving the query "params" but not getting the "File Name"
My Storage model name is container
Container.beforeRemote('upload', function(ctx, modelInstance, next) {
//OUPTUTS: {orderId:1, customerId:1]}
console.log(ctx.req.query);
//Now I want to change the File Name of the file.
//But not getting how to do that
next();
})
How to change the File name of the File getting saved at the server?
I figured it out.
We have to define a custom function getFileName in boot/configure-storage.js.
Suppose my datasource for loopback-component-storage is presImage.
server/boot/configure-storage.js
module.exports = function(app) {
//Function for checking the file type..
app.dataSources.presImage.connector.getFilename = function(file, req, res) {
//First checking the file type..
var pattern = /^image\/.+$/;
var value = pattern.test(file.type);
if(value ){
var fileExtension = file.name.split('.').pop();
var container = file.container;
var time = new Date().getTime();
var query = req.query;
var customerId = query.customerId;
var orderId = query.orderId;
//Now preparing the file name..
//customerId_time_orderId.extension
var NewFileName = '' + customerId + '_' + time + '_' + orderId + '.' + fileExtension;
//And the file name will be saved as defined..
return NewFileName;
}
else{
throw "FileTypeError: Only File of Image type is accepted.";
}
};
}
common/models/container.js
Now suppose my container model is container.
module.exports = function(Container) {
Container.afterRemote('upload', function(ctx, modelInstance, next) {
var files = ctx.result.result.files.file;
for(var i=0; i<files.length; i++){
var ModifiedfileName = files[i].name;
console.log(ModifiedfileName) //outputs the modified file name.
} //for loop
next();
}); //afterRemote..
};
Now for converting it images to Thumbnail size
Download the quickthumb
Here is how to use it with loopback.
This code is copied directly from Loopback thumbnail view
common/models/container.js
module.exports = function(Container) {
var qt = require('quickthumb');
Container.afterRemote('upload', function(ctx, res, next) {
var file = res.result.files.file[0];
var file_path = "./server/storage/" + file.container + "/" + file.name;
var file_thumb_path = "./server/storage/" + file.container + "/thumb/" + file.name;
qt.convert({
src: file_path,
dst: file_thumb_path,
width: 100
}, function (err, path) {
});
next();
});
};
Piggybacking on the answer above, this configure-storage enables the file name to be set explicitly via req.params.filename and to default to the existing name if none is provided.
configure-storage.js
module.exports = function(app) {
//Function for checking the file type..
app.dataSources.storage.connector.getFilename = function(file, req, ignoreRes) {
if (!req.params.filename) {
return file.name
}
var fileExtension = file.name.split('.').pop()
return req.params.filename + '.' + fileExtension
};
}
I've been able to successfully write binary data (an image) to MongoDB in Node.js. However I can't find clear documentation on how to read it back.
Here's how I'm writing the image to MongoDB:
var imageFile = req.files.myFile;
var imageData = fs.readFileSync(imageFile.path);
var imageBson = {};
imageBson.image = new db.bson_serializer.Binary(imageData);
imageBson.imageType = imageFile.type;
db.collection('images').insert(imageBson, {safe: true},function(err, data) {
I'd appreciate any pointers on reading the image from Mongo using Node. I'm assuming there's a function like "db.bson_deserializer...". Thanks!
Found the answer:
var imageFile = req.files.myFile;
fs.exists(imageFile.path, function(exists) {
if(exists)
{
console.log("File uploaded: " + util.inspect(imageFile));
fs.readFile(imageFile.path, function(err, imageData) {
if (err) {
res.end("Error reading your file on the server!");
}else{
//when saving an object with an image's byte array
var imageBson = {};
//var imageData = fs.readFileSync(imageFile.path);
imageBson.image = new req.mongo.Binary(imageData);
imageBson.imageType = imageFile.mimetype;
console.log("imageBson: " + util.inspect(imageBson));
req.imagesCollection.insert(imageBson, {safe: true},function(err, bsonData) {
if (err) {
res.end({ msg:'Error saving your file to the database!' });
}else{
fs.unlink(imageFile.path); // Deletes the file from the local disk
var imageBson = bsonData[0];
var imageId = imageBson._id;
res.redirect('images/' + imageId);
}
});
}
});
} else {
res.end("Oddly your file was uploaded but doesn't seem to exist!\n" + util.inspect(imageFile));
}
});
The MongoDB part isn't complicated. Once a Buffer is in the model, just let the db save it. MongoDB converts that into BinData. 80% of this code is just getting an image into and out of a PNG file.
People say don't store images in MongoDB, but icons/thumbs are tiny. Having said that, it might be a good idea to have an icons collection and only store them once using a hash of the image data as the _id.
model class example
class MyModel {
_icon: Buffer
get icon(): Buffer {
return this._icon
}
set icon(value: Buffer) {
this._icon = value
}
}
image helper
static async loadImage(url: string) {
var files = require('../lib/files')
var buffer = await files.urlContents(url, true)
return buffer
}
static async saveImage(image: Buffer, path: string) {
var files = require('../lib/files')
files.write(path, image.buffer)
return path
}
files helper
function urlResponse(url, binary) {
var request = require("request")
if (binary)
request = request.defaults({ encoding: null })
return new Promise(function (resolve, reject) {
request(url, function (error, res, body) {
if (error || res.statusCode !== 200 || body.includes('Incomplete response received from application'))
resolve({ statusCode: res?.statusCode !== 200 ? (res?.statusCode || 500) : 500 });
else
resolve(res);
});
});
}
async function urlContents(url, binary) {
var res = await urlResponse(url, binary)
if (binary)
return Buffer.from(res.body)
else
return res.body
}
function write(fileName, contents) {
fs.writeFileSync(fileName, contents)
}
mongodb helper
// ...saving
myModel.icon = loadImage('http://some.site.com/image.png')
collection.insertOne(myModel)
// ..getting
myModel = collection.findOne(query) // now myModel contains icon
saveImage(myModel.icon, '/home/toddmo/pictures/wow.png')
I have a function on nodejs that generates an image from many images and then generate a pdf file from that. Im trying with just one image but i need to add more, but this doesnt seems to work
function HelperHandler() {
this.pdf = function(req, res, next) {
var doc = new PDFDocument;
mergeImages(function(err, image) {
if (err)
return res.json(err);
doc.image(image, 100, 100);
doc.output(function(string) {
res.contentType = "application/pdf";
res.send(string);
});
})
}
}
var mergeImages = function(callback) {
var Canvas = require("canvas")
, fs = require("fs");
fs.readFile(global.root_path + "/images/bg.jpg", function(err, data) {
if (err)
callback("error loading image");
else {
var canvas = new Canvas(408, 939)
, img = new Canvas.Image(data);
ctx = canvas.getContext("2d");
img.onload = function() {
ctx.drawImage(img, 0, 0, 408, 939);
}
canvas.toDataURL('image/png', function(err, str) {
callback(null, str);
});
}
});
}
Error
Error: ENAMETOOLONG, name too long 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZgAAAOrCAYAAABqSpB/AAAABmJLR0QA/wD/AP+gvaeTAAAF5UlEQVR4nO3BMQEAAADCoPVPbQo/oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICjAWckAAHF4EUcAAAAAElFTkSuQmCC'
at Object.openSync (fs.js:427:18)
at Object.readFileSync (fs.js:284:15)
at Function.open (/Users/jtomasrl/code/app/server/node_modules/pdfkit/js/image.js:27:28)
at PDFDocument.image (/Users/jtomasrl/code/app/server/node_modules/pdfkit/js/mixins/images.js:27:26)
at /Users/jtomasrl/code/app/server/lib/handler/current/helper.js:15:11
at /Users/jtomasrl/code/app/server/lib/handler/current/helper.js:41:9
at /Users/jtomasrl/code/app/server/node_modules/canvas/lib/canvas.js:217:7
You can use a buffer or a path with PDFKit image.
But you can't use a base64 URL, you need to decode this string to a buffer.
To use base64 data:
doc.image(new Buffer(image.replace('data:image/png;base64,',''), 'base64'), 100, 100); // this will decode your base64 to a new buffer
More information on base64 de/encode with Node Buffer here.