Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/client side resizing #41

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion app.js
Expand Up @@ -62,7 +62,7 @@ module.exports = {
app.use(favicon(path.join(__dirname, 'public/img/Softwerkskammer16x16.ico')));
app.use(morgan('combined', {stream: winstonStream}));
app.use(cookieParser());
app.use(bodyparser.urlencoded({extended: true}));
app.use(bodyparser.urlencoded({extended: true, limit: '50mb'}));
app.use(compress());
app.use(serveStatic(path.join(__dirname, 'public'), { maxAge: 600 * 1000 })); // ten minutes

Expand Down
74 changes: 46 additions & 28 deletions lib/activityresults/index.js
@@ -1,5 +1,6 @@
'use strict';
var beans = require('nconf').get('beans');
var bodyParser = require("body-parser");
var ActivityResult = beans.get('activityresult');
var activityresultsPersistence = beans.get('activityresultsPersistence');
var activityresultsService = beans.get('activityresultsService');
Expand Down Expand Up @@ -58,41 +59,58 @@ app.post('/', function (req, res) {
});
});

app.post("/:activityResultName/upload", function (req, res) {
new Form().parse(req, function (err, fields, files) {
if (!files) {
return res.send(400);
var processUploadRequestWithImageUri = function (imageUri, req, res) {
galleryRepository.getMetadataForImage(imageUri, function (err, metadata) {
var date;
if (metadata && metadata.exif) {
date = metadata.exif.dateTime ||
metadata.exif.dateTimeOriginal ||
metadata.exif.dateTimeDigitized ||
new Date();
} else {
date = new Date();
}
var image = files.image[0];
galleryRepository.storeImage(image.path, function (err, imageUri) {

var newPhoto = {
id: uuid.v4(),
uri: galleryApp.path() + imageUri,
timestamp: date,
uploaded_by: req.user.member.state.id
};

activityresultsService.addPhotoToActivityResult(req.params.activityResultName, newPhoto, function (err) {
if (req.header('X-Requested-With') === 'XMLHttpRequest') {
return res.send(200, app.path() + req.params.activityResultName + '/photo/' + newPhoto.id + '/edit');
}
res.location(app.path() + req.params.activityResultName + '/photo/' + newPhoto.id + '/edit');
res.send(303);
});
});
};

app.post("/:activityResultName/upload", bodyParser({limit: '50mb'}), function (req, res) {
if (req.body.photo) {
// DataURL
galleryRepository.storeImageFromDataURL(req.body.photo, function (err, imageUri) {
if (err) {
throw err;
}
galleryRepository.getMetadataForImage(imageUri, function (err, metadata) {
var date;
if (metadata && metadata.exif) {
date = metadata.exif.dateTime ||
metadata.exif.dateTimeOriginal ||
metadata.exif.dateTimeDigitized ||
new Date();
} else {
date = new Date();
processUploadRequestWithImageUri(imageUri, req, res);
});
} else {
new Form().parse(req, function (err, fields, files) {
if (!files) {
return res.send(400);
}
var image = files.image[0];
galleryRepository.storeImage(image.path, function (err, imageUri) {
if (err) {
throw err;
}

var newPhoto = {
id: uuid.v4(),
uri: galleryApp.path() + imageUri,
timestamp: date,
uploaded_by: req.user.member.state.id
};

activityresultsService.addPhotoToActivityResult(req.params.activityResultName, newPhoto, function (err) {
res.location(app.path() + req.params.activityResultName + '/photo/' + newPhoto.id + '/edit');
res.send(303);
});
processUploadRequestWithImageUri(imageUri, req, res);
});
});
});
}
});

app.get("/:activityResultName/photo/:photoId/edit", function (req, res) {
Expand Down
68 changes: 67 additions & 1 deletion lib/activityresults/views/get.jade
Expand Up @@ -46,9 +46,11 @@ block content
div
button(type="submit").btn.btn-success.pull-right= t('activityresults.submit')
span#btn-cancel.btn.btn-warning= t('activityresults.cancel')
input#resizeOnClient(type='checkbox',name='resizeOnClient',checked='checked')
label(for='resizeOnClient')= t('activityresults.resize_on_client')


script.
script.
function getPreview(files, callback) {
if(!files || !files[0])
return null;
Expand Down Expand Up @@ -96,6 +98,70 @@ block content
span.small(style="color: black")
+thumbnailInfos(img)

script(type='text/javascript').
function resizeImageFromFile(file, MAX_WIDTH, MAX_HEIGHT, callback) {
// Load via FileReader
var fr = new FileReader();
fr.onload = function(e) {
getDimensions(e.target.result, function(width, height, img) {
var newWidth = width;
var newHeight = height;
var aspectRatio = width/height;
// Image is wider than tall
if(width > MAX_WIDTH && aspectRatio >= 1) {
newWidth = MAX_WIDTH;
newHeight = MAX_WIDTH / aspectRatio
} else if(height > MAX_HEIGHT && aspectRatio <= 1) {
newWidth = MAX_HEIGHT * aspectRatio;
newHeight = MAX_HEIGHT;
}
resizeImage(img, newWidth, newHeight, function (canvas) {
callback(canvas.toDataURL('image/jpeg', 0.8));
})
});
}
fr.readAsDataURL(file);
function resizeImage(imgElement, width, height, callback) {
var cvs = document.createElement("canvas");
cvs.width = width;
cvs.height = height;
var ctx = cvs.getContext('2d');
ctx.drawImage(imgElement, 0, 0, width, height);
callback(cvs);
}
function getDimensions(imgData, callback) {
var tmpImg = document.createElement("img");
tmpImg.onload = function () {
callback(tmpImg.width, tmpImg.height, tmpImg);
}
tmpImg.src = imgData;
}
}

$(function() {
var $recordForm = $('#recordForm');
$recordForm.on('submit', function (e) {
if (!$('#resizeOnClient').is(':checked')) {
console.log('Not resizing image on client');
return;
}
var files = $('#input-file')[0].files;
if(!files || !files[0]) {
return;
}
resizeImageFromFile(files[0], 2560, 2560, function (dataURL) {
$.post($recordForm.attr('action'), {
photo: dataURL
}).success(function (data) {
window.location.href = data;
}).error(function (error) {
alert(error);
});
})
e.preventDefault();
});
});

script(type='text/javascript').
var ESC_KEY = 27;
jQuery(function() {
Expand Down
24 changes: 22 additions & 2 deletions lib/gallery/galleryrepositoryService.js
Expand Up @@ -5,6 +5,8 @@ var logger = require('winston').loggers.get('gallery');
var magick = require('imagemagick');
var uuid = require('node-uuid');
var path = require('path');
var tmp = require('tmp');
var dataurl = require('dataurl');

function autoOrient(sourceImagePath, targetPath, callback) {
if (logger.debug) {
Expand All @@ -25,7 +27,7 @@ function scale(sourceImagePath, targetPath, width, height, fn) {
if (logger.debug) {
logger.debug('Scaling `' + sourceImagePath + '\' to ' + width + 'x' + height + ' into `' + targetPath + '\'');
}
magick.convert([sourceImagePath, '-scale', width + '!x' + height + '!', targetPath], function (err, stdout) {
magick.convert([sourceImagePath, '-quality', '75', '-scale', width + '!x' + height + '!', targetPath], function (err, stdout) {
if (err) {
return fn(err, undefined);
}
Expand All @@ -40,7 +42,7 @@ function scaleWidth(sourceImagePath, targetPath, width, fn) {
if (logger.debug) {
logger.debug('Scaling `' + sourceImagePath + '\' to ' + width + 'x undefined into `' + targetPath + '\'');
}
magick.convert([sourceImagePath, '-scale', width, targetPath], function (err, stdout) {
magick.convert([sourceImagePath, '-quality', '75', '-scale', width, targetPath], function (err, stdout) {
if (err) {
return fn(err, undefined);
}
Expand Down Expand Up @@ -75,6 +77,24 @@ module.exports = {
});
},

storeImageFromDataURL: function storeImageFromDataURL(dataURL, callback) {
var _this = this;

var dataPackage = dataurl.parse(dataURL);
if (!dataPackage || !dataPackage.mimetype.match(/image\/(jpg|jpeg)/)) {
return callback(new Error('Not a valid dataURL'));
}
// Write to tmpFile
tmp.file({postfix: '.jpg'}, function (err, path, fd) {
_this.fs().writeFile(path, dataPackage.data, {encoding: dataPackage.encoding}, function (err) {
if (err) {
return callback(err);
}
_this.storeImage(path, callback);
});
});
},

getMetadataForImage: function getMetadataForImage(id, callback) {
var persistentImageFilePath = this.directory() + '/' + id;
magick.readMetadata(persistentImageFilePath, callback);
Expand Down
3 changes: 2 additions & 1 deletion locales/translation-de.json
Expand Up @@ -175,7 +175,8 @@
"done": "Fertig",
"record_image": "Aufzeichnen",
"submit": "Weiter",
"photoTitlePlaceholder": "Was kann man hier sehen?"
"photoTitlePlaceholder": "Was kann man hier sehen?",
"resize_on_client": "Bild auf Gerät skalieren"
},
"payment": {
"title": "Zahlung",
Expand Down
3 changes: 2 additions & 1 deletion locales/translation-en.json
Expand Up @@ -174,7 +174,8 @@
"done": "Done",
"photoTitlePlaceholder": "What's on this photo?",
"record_image": "Record",
"submit": "SUBMIT"
"submit": "SUBMIT",
"resize_on_client": "Resize image on client"
},
"payment": {
"title": "Payment",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -36,6 +36,7 @@
"cookie-parser": "1.3.2",
"CoolBeans": "0.0.9",
"csurf": "1.4.0",
"dataurl": "^0.1.0",
"express": "4.7.2",
"express-session": "1.7.2",
"i18next": "1.7.4",
Expand Down Expand Up @@ -64,6 +65,7 @@
"soap-sympa": "0.0.1",
"static-favicon": "1.0.2",
"stripe": "2.8.0",
"tmp": "0.0.24",
"underscore.string": "2.3.3",
"URIjs": "1.13.2",
"useragent": "2.0.9",
Expand Down
17 changes: 17 additions & 0 deletions test/activityresults/activityresults_integration_upload_test.js
Expand Up @@ -45,4 +45,21 @@ describe('/activityresults/:result/upload', function () {
.end(done);
});
});

describe('POST / with dataURL', function () {
it("should store the image via gallery service and redirect to edit", function (done) {
var dataURL = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCADIAMgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8qqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/2Q==';
sinon.stub(galleryRepository, 'storeImage', function (tmpFile, callback) {
callback(null, "my-custom-image-id");
});

//noinspection JSLint
request(createApp(1))
.post('/foo/upload')
.send({photo: dataURL})
.expect(303)
.expect('Location', /\/foo\/photo\/[\w+|\-]+\/edit/)
.end(done);
});
});
});
12 changes: 12 additions & 0 deletions test/gallery/galleryrepository_test.js
Expand Up @@ -48,6 +48,18 @@ describe("the gallery repository on real files", function () {
});
});

it('stores a dataurl image', function (done) {
var dataURL = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCADIAMgDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8qqKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/2Q==';
service.storeImageFromDataURL(dataURL, function (err, imageId) {
service.retrieveImage(imageId, function (err) {
if (err) {
done(err);
}
done();
});
});
});

it('provides exif data for a given image', function (done) {
var storedImageId = 'exif_image.jpg';
var imagePath = __dirname + '/fixtures/' + storedImageId;
Expand Down