diff --git a/.travis.yml b/.travis.yml index f5008cf5..0c484b76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,19 +4,11 @@ notifications: email: false node_js: - '8' + - '10' before_script: - npm prune branches: except: - /^v\d+\.\d+\.\d+$/ -env: - - CXX=g++-4.9 -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-4.9 before_install: - - if [[ `npm -v` != 5* ]]; then npm i -g npm@latest; fi - sudo apt-get install libjpeg-dev libgif-dev diff --git a/dadi/lib/controller/index.js b/dadi/lib/controller/index.js index f939d897..17d30535 100755 --- a/dadi/lib/controller/index.js +++ b/dadi/lib/controller/index.js @@ -145,7 +145,7 @@ const Controller = function (router) { pattern = pattern.concat([ parsedUrl.pathname, - parsedUrl.search.slice(1) + parsedUrl.search ? parsedUrl.search.slice(1) : null ]) } @@ -250,7 +250,7 @@ Controller.prototype.addCacheControlHeader = function (res, handler, domain) { if (!value || (value.length === 0)) return // already set - if (res._headers['cache-control']) return + if (res.getHeader('cache-control')) return // set the header res.setHeader('Cache-Control', value) diff --git a/dadi/lib/handlers/image.js b/dadi/lib/handlers/image.js index 407e4da9..fbdc735d 100644 --- a/dadi/lib/handlers/image.js +++ b/dadi/lib/handlers/image.js @@ -14,6 +14,8 @@ const sharp = require('sharp') const smartcrop = require('smartcrop-sharp') const urlParser = require('url') const Vibrant = require('node-vibrant') +const imagemin = require('imagemin') +const imageminJpegtran = require('imagemin-jpegtran') const StorageFactory = require(path.join(__dirname, '/../storage/factory')) const Cache = require(path.join(__dirname, '/../cache')) @@ -61,7 +63,8 @@ const IMAGE_PARAMETERS = [ { name: 'blur', aliases: ['b'] }, { name: 'strip', aliases: ['s'] }, { name: 'rotate', aliases: ['r'] }, - { name: 'flip', aliases: ['fl'] } + { name: 'flip', aliases: ['fl'] }, + { name: 'progressive', aliases: ['pg'], default: 'true' } ] /** @@ -792,9 +795,9 @@ ImageHandler.prototype.process = function (sharpImage, imageBuffer) { will _not_ be preserved. */ case 'fill': + resizeOptions.fit = 'fill' sharpImage = sharpImage .resize(width, height, resizeOptions) - .ignoreAspectRatio() break @@ -822,7 +825,7 @@ ImageHandler.prototype.process = function (sharpImage, imageBuffer) { // resize if options.width or options.height are explicitly set if (options.width || options.height) { if (options.width && options.height) { - sharpImage = sharpImage.ignoreAspectRatio() + resizeOptions.fit = 'fill' } if (options.devicePixelRatio && options.devicePixelRatio < 4) { @@ -989,6 +992,10 @@ ImageHandler.prototype.process = function (sharpImage, imageBuffer) { processBuffer = this.processGif(buffer) } + if (options.progressive === 'true' && (format === 'jpeg' || format === 'jpg')) { + processBuffer = this.progressiveJpeg(buffer) + } + processBuffer.then(buffer => { resolve(buffer) }) @@ -1030,6 +1037,21 @@ ImageHandler.prototype.processGif = function (buffer) { }) } +/** + * Transcodes an input buffer to a progressive JPEG + * + * @param {Buffer} buffer - a Buffer extracted from the main image + * processor after applying image manipulations + * @returns {Buffer} a progressive JPEG encoded buffer + */ +ImageHandler.prototype.progressiveJpeg = function (buffer) { + return imagemin.buffer(buffer, { + plugins: [ + imageminJpegtran({progressive: true}) + ] + }) +} + ImageHandler.prototype.sanitiseOptions = function (options) { // check the options for aliases // e.g. "dpr" === "devicePixelRatio" @@ -1153,7 +1175,8 @@ function getImageOptionsFromLegacyURL (optionsArray) { blur: optionsArray[9 + superLegacyFormatOffset], strip: optionsArray[10 + superLegacyFormatOffset], rotate: optionsArray[11 + superLegacyFormatOffset], - flip: optionsArray[12 + superLegacyFormatOffset] + flip: optionsArray[12 + superLegacyFormatOffset], + progressive: optionsArray[13 + superLegacyFormatOffset] } return options diff --git a/package.json b/package.json index 20e82677..6983e128 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,8 @@ "he": "^1.1.0", "husky": "^1.0.1", "image-size-stream": "1.1.0", + "imagemin": "^6.0.0", + "imagemin-jpegtran": "^5.0.2", "images": "^3.0.0", "jimp": "^0.2.28", "jsonwebtoken": "^8.2.1", diff --git a/test/acceptance/auth.js b/test/acceptance/auth.js index a68ba527..535b2b84 100755 --- a/test/acceptance/auth.js +++ b/test/acceptance/auth.js @@ -62,7 +62,10 @@ describe('Authentication', function () { .send({pattern: 'test'}) .set('Authorization', 'Bearer ' + token) .expect('content-type', 'application/json') - .expect(200, done) + .end((err, res) => { + res.statusCode.should.eql(200) + done() + }) }) }) diff --git a/test/acceptance/help.js b/test/acceptance/help.js index 05d44a76..a30d3442 100755 --- a/test/acceptance/help.js +++ b/test/acceptance/help.js @@ -137,12 +137,20 @@ module.exports.clearCache = function () { if (fs.lstatSync(curPath).isDirectory()) { // recurse deleteFolderRecursive(curPath) } else { // delete file - fs.unlinkSync(path.resolve(curPath)) + try { + fs.unlinkSync(path.resolve(curPath)) + } catch (err) { + + } } }) fs.rmdirSync(filepath) } else { - fs.unlinkSync(filepath) + try { + fs.unlinkSync(filepath) + } catch (err) { + + } } } diff --git a/test/acceptance/visual_manifest.json b/test/acceptance/visual_manifest.json index 3f120904..6e62968f 100644 --- a/test/acceptance/visual_manifest.json +++ b/test/acceptance/visual_manifest.json @@ -297,6 +297,13 @@ "quality": "100" }, "baselineFilename": "images/visual/baseline/measure.png?quality=100.png" + }, + { + "image": "measure2.jpg", + "params": { + "progressive": true + }, + "baselineFilename": "images/visual/baseline/measure2.jpg?progressive=true.jpg" } ] } \ No newline at end of file diff --git a/test/images/visual/baseline/measure2.jpg?progressive=true.jpg b/test/images/visual/baseline/measure2.jpg?progressive=true.jpg new file mode 100644 index 00000000..f431c4e1 Binary files /dev/null and b/test/images/visual/baseline/measure2.jpg?progressive=true.jpg differ