├── index.js ├── .gitignore ├── src ├── index.js ├── filters │ └── hermite.js └── blitz.js ├── .dist.babelrc ├── MIT-LICENSE.txt ├── package.json ├── demo.html ├── README.md ├── dist ├── blitz.min.js └── blitz.js └── tests └── blitz._heightWeightCalc.js /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const Blitz = require('./src/index.js') 3 | module.exports = Blitz 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .env 3 | .idea 4 | .DS_Store 5 | coverage/* 6 | test.js 7 | test.html 8 | .vscode/* 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Blitz = require('./blitz.js') 4 | 5 | Blitz.addFilter( require('./filters/hermite.js') ) 6 | 7 | module.exports = Object.create(Blitz) 8 | exports = module.exports -------------------------------------------------------------------------------- /.dist.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { 4 | "targets": { 5 | "browsers": ["> 1%", "last 2 versions"] 6 | } 7 | }] 8 | ], 9 | "sourceMaps": "inline" 10 | } 11 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) ViliusL 2 | https://github.com/viliusle 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blitz-resize", 3 | "version": "0.4.0", 4 | "description": "Most versatile, powerful, and fastest way to resize an image. Fast, non-blocking (does not freeze windows), and async/await/promise compatible.", 5 | "main": "index.js", 6 | "browser": "dist/blitz.min.js", 7 | "scripts": { 8 | "test": "mocha './tests/' --recursive --exit -r esm", 9 | "browserify": "browserify src/index.js > dist/blitz.js --standalone Blitz --global-transform [ babelify --configFile ./.dist.babelrc ]", 10 | "browserify:minify": "browserify src/index.js > dist/blitz.min.js --standalone Blitz --global-transform [ babelify --configFile ./.dist.babelrc ] -p tinyify", 11 | "build": "npm run browserify && npm run browserify:minify" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/calvintwr/blitz-hermite-resize.git" 16 | }, 17 | "keywords": [ 18 | "resize", 19 | "image", 20 | "compression", 21 | "imageresizer", 22 | "imagemagick", 23 | "graphicsmagick" 24 | ], 25 | "author": "calvintwr", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/calvintwr/blitz-hermite-resize/issues" 29 | }, 30 | "homepage": "https://github.com/calvintwr/blitz-hermite-resize#readme", 31 | "devDependencies": { 32 | "@babel/core": "^7.10.2", 33 | "@babel/preset-env": "^7.10.2", 34 | "babelify": "^10.0.0", 35 | "browserify": "^16.5.1", 36 | "chai": "^4.2.0", 37 | "esm": "^3.2.25", 38 | "mocha": "^8.0.1", 39 | "mocha-sinon": "^2.1.2", 40 | "sinon": "^9.0.2", 41 | "sinon-chai": "^3.5.0", 42 | "tinyify": "^2.5.2" 43 | }, 44 | "files": [ 45 | "index.js", 46 | "src", 47 | "dist" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /src/filters/hermite.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const name = 'hermite' 4 | 5 | const filter = function (event) { 6 | var data = new Uint8ClampedArray(event.data[0]); 7 | var W = event.data[1]; 8 | var H = event.data[2]; 9 | var img = new ImageData( 10 | data, 11 | W, 12 | H 13 | ) 14 | 15 | var data2 = new Uint8ClampedArray(event.data[5]); 16 | var W2 = event.data[3]; 17 | var H2 = event.data[4]; 18 | var img2 = new ImageData( 19 | data2, 20 | W2, 21 | H2 22 | ) 23 | 24 | var ratio_w = W / W2; 25 | var ratio_h = H / H2; 26 | var ratio_w_half = Math.ceil(ratio_w/2); 27 | var ratio_h_half = Math.ceil(ratio_h/2); 28 | 29 | for(var j = 0; j < H2; j++) { 30 | for(var i = 0; i < W2; i++) { 31 | var x2 = (i + j*W2) * 4; 32 | var weight = 0; 33 | var weights = 0; 34 | var gx_r = gx_g = gx_b = gx_a = 0; 35 | var center_y = (j + 0.5) * ratio_h; 36 | for(var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) { 37 | var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half; 38 | var center_x = (i + 0.5) * ratio_w; 39 | var w0 = dy*dy //pre-calc part of w 40 | for(var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) { 41 | var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half; 42 | var w = Math.sqrt(w0 + dx*dx); 43 | if(w >= -1 && w <= 1){ 44 | //hermite filter 45 | weight = 2 * w*w*w - 3*w*w + 1; 46 | if(weight > 0) { 47 | dx = 4*(xx + yy*W); 48 | gx_r += weight * data[dx]; 49 | gx_g += weight * data[dx + 1]; 50 | gx_b += weight * data[dx + 2]; 51 | gx_a += weight * data[dx + 3]; 52 | weights += weight; 53 | } 54 | } 55 | } 56 | } 57 | 58 | data2[x2] = gx_r / weights; 59 | data2[x2 + 1] = gx_g / weights; 60 | data2[x2 + 2] = gx_b / weights; 61 | data2[x2 + 3] = gx_a / weights; 62 | 63 | } 64 | } 65 | return img2.data.buffer; 66 | } 67 | 68 | module.exports = { 69 | name, 70 | filter 71 | } -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

File input test

11 |
12 |

File to Data:

13 | 14 |
15 |
16 |

Image to Canvas:

17 |
18 |
19 |
20 |

Data to Image:

21 |
22 |
23 |
24 |

Canvas to Image:

25 |
26 |
27 | 28 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Black with Thunder Icon Basketball Logo-2](https://user-images.githubusercontent.com/6825277/87935398-9fffdb00-cac3-11ea-9daf-0139489853f9.png) 2 | 3 | **Blitz is the fastest, most versatile, and powerful way to resize an image. It is designed to be non-blocking (does not freeze browser UI), and async/await/promise compatible.** 4 | 5 | **Blitz resizes high resolution DSLR images in a matter of seconds. Precipitously cut upload time by resizing your image on the client-side, yet achieve high quality and performance.** 6 | 7 | # Blitz 8 | ## Installation 9 | 10 | ``` 11 | npm install blitz-resize --save 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```js 17 | const blitz = Blitz.create() 18 | 19 | /* Promise */ 20 | blitz({ 21 | source: DOM Image/DOM Canvas/jQuery/DataURL/File, 22 | width: 400, 23 | height: 600 24 | }).then(output => { 25 | // handle output 26 | }).catch(error => { 27 | // handle error 28 | }) 29 | 30 | /* Await */ 31 | let resized = await blitz({...}) 32 | 33 | /* Old school callback */ 34 | const blitz = Blitz.create('callback') 35 | blitz({...}, function(output) { 36 | // run your callback. 37 | }) 38 | 39 | ``` 40 | 41 | ## Why use Blitz 42 | 43 | Precipitously cut image upload time and server loads by doing client-side image resizing. Blitz is non-blocking so you will not experience UI freeze when it is resizing. Advanced scaling options with max or min height/width. 44 | 45 | ## Full options 46 | ```js 47 | blitz({ 48 | source: DOM Image/DOM Canvas/jQuery/DataURL/Javscript #File, 49 | 50 | // when only 1 is defined, the other will be scaled proportionally by default. 51 | width: (number), // optional 52 | height: (number), // optional 53 | 54 | // if width/height is defined, all max and mins are ignored 55 | maxWidth: (number), 56 | maxHeight: (number), 57 | minWidth: (number), 58 | minHeight: (number), 59 | 60 | proportional: true (default)/false, // if set to false, resizing will not attempt to maintain proportions 61 | 62 | // [optional] jpg, gif, png or raw. when not defined, assumes png. 63 | outputFormat: 'jpg', 64 | 65 | // [optional] `image`, `canvas`, `data`, `download`, `blob`, or `flie` for Javascript #File. 66 | // If not entered output is same as input format. 67 | output: 'data', 68 | 69 | // [optional] applicable for `image`, `file` or `data` output only 70 | quality: 0.7, // between 0 to 1. 71 | 72 | // [optional] if you want to know how fast blitz resize 73 | logPerformance: true/false 74 | }).then(output => { 75 | // outputs: 76 | 77 | // 'image' -- Image : This will be the Image DOM, which you can append to your DOM. 78 | // 'canvas' -- HTML Canvas 79 | // 'data' -- DataURL : You can attach it to or just redirect to it to show on browser. 80 | // 'download' -- : You need to call this function to run the download. 81 | // 'blob' -- Blob : Javascript Blob object 82 | // 'file' -- File : Javascript File object 83 | 84 | 85 | }).catch(err => { 86 | // handle err 87 | }) 88 | ``` 89 | 90 | ## Examples 91 | ```html 92 | 93 | 205 | ``` 206 | 207 | ## License 208 | 209 | *Blitz* is MIT licensed. 210 | -------------------------------------------------------------------------------- /dist/blitz.min.js: -------------------------------------------------------------------------------- 1 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Blitz=t()}}(function(){var t={};function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}return(t=t={resize:function(t,i){var a=this;if("object"!=e(t))throw Error("You need to provide options for Blitz as a `object`.");for(var n=["source"],r=0;r-1)o=this._canvasToImageOrData(e,i.mimeType,i.quality,i.output);else if("file"===i.output)o=this._canvasToFile(e,i.mimeType,i.quality,i.output);else if("blob"===i.output)o=this._canvasToBlob(e,i.mimeType,i.quality,i.output);else if("download"===i.output)o=this._download(this,e,i);else{if(void 0!==i.output)throw Error("`output` is not valid.");var s;if("DATA"===sourceElement.type?s="data":"IMG"===sourceElement.type&&(s="image"),s)return this._canvasToImageOrData(e,i.mimeType,i.quality,s);o=e}return"function"==typeof r?r(o):o},_heightWidthCalc:function(t,e,i,a,n,r,o,h,c){var d={height:t,width:e};if(i&&a)return(n||r||o||o)&&console.warn("Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied."),c&&console.warn("Warn: `proportional` is ignored when both `height` and `width` are provided."),{height:i,width:a};if(null==c&&(c="defaulted true"),a)return(n||r||o||o)&&console.warn("Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied."),{width:a,height:c?this._scale(e,t,a):t};if(i)return(n||r||o||o)&&console.warn("Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied."),{height:i,width:c?this._scale(t,e,i):e};var s=[];if(o&&n&&o>n&&s.push("`minHeight` cannot be larger than `maxHeight."),h&&r&&h>r&&s.push("`minWidth` cannot be larger than `maxWidth."),s.length>0)throw Error(s.join(" "));if(r&&n&&re&&o>t){if(!1===c)return{width:h,height:o};var f={width:h,height:this._scale(e,t,h)},l={width:this._scale(t,e,o),height:o};return f.width*f.height>l.width*l.height?m(f,{maxH:n},c):m(l,{maxW:r},c)}return h&&h>e?m({width:h,height:c?this._scale(e,t,h):t},{maxH:n},c):o&&o>t?m({width:c?this._scale(t,e,o):e,height:o},{maxW:r},c):d;function m(t,e,i){var a=e.minH,n=e.minW,r=e.maxH,o=e.maxW,h="Warn: Unable to maintain image proportionality.";return a?t.heightr?(!0===i&&console.warn("".concat(h," Enforcing maxHeight of ").concat(r," as scaling image to minWidth of ").concat(t.width," gives scaled height of ").concat(t.height,".")),{height:r,width:t.width}):t:o&&t.width>o?(!0===i&&console.warn("".concat(h," Enforcing maxWidth of ").concat(o," as scaling image to minHeight of ").concat(t.height," gives scaled width of ").concat(t.width,".")),{height:t.height,width:o}):t}},_scale:function(t,e,i){return Math.round(i/(t/e))},_extract:function(t){if(t instanceof File)return{source:t,type:"FILE"};if("string"==typeof t&&0===t.indexOf("data"))return{source:t,type:"DATA"};if(t.tagName)return{source:t,type:t.tagName};if(t[0].tagName)return{source:t[0],type:t[0].tagName};throw Error("`source` element to be invalid.")},_imageToCanvas:function(t){var e=document.createElement("canvas"),i=e.getContext("2d");return e.height=t.height,e.width=t.width,i.drawImage(t,0,0,t.width,t.height),e},_dataToCanvas:function(t,e,i){var a=this,n=new Image;n.src=t,n.onload=function(){var t=document.createElement("canvas"),r=t.getContext("2d");t.height=n.height,t.width=n.width,r.drawImage(n,0,0,n.width,n.height),e.output=e.output?e.output:"data",e.source=t,a.resize(e,i)}},_fileToCanvas:function(t,e,i){var a=this,n=new FileReader;n.onload=function(t){e.source=t.target.result,e.output||(e.output="file"),a.resize(e,i)},n.readAsDataURL(t)},_canvasToImageOrData:function(t,e,i,a){var n,r=t.toDataURL(e,i);return"data"===a?n=r:(n=new Image).src=t.toDataURL(e,i),n},_canvasToBlob:function(t,e,i,a){var n=this._canvasToImageOrData(t,e,i,"data");return this._dataURItoBlob(n)},_canvasToFile:function(t,e,i,a){var n=this._canvasToBlob(t,e,i);return new File([n],"resized",{type:e,lastModified:Date.now()})},_dataURItoBlob:function(t){var e;e=t.split(",")[0].indexOf("base64")>=0?atob(t.split(",")[1]):unescape(t.split(",")[1]);for(var i=t.split(",")[0].split(":")[1].split(";")[0],a=new Uint8Array(e.length),n=0;n=-1&&C<=1&&(m=2*C*C*C-3*C*C+1)>0&&(w+=m*e[W=4*(T+y*i)],gx_g+=m*e[W+1],gx_b+=m*e[W+2],gx_a+=m*e[W+3],p+=m)}n[l]=w/p,n[l+1]=gx_g/p,n[l+2]=gx_b/p,n[l+3]=gx_a/p}return h.data.buffer}}),Object.create(t)}); -------------------------------------------------------------------------------- /tests/blitz._heightWeightCalc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Blitz = require('../src/blitz.js') 4 | const chai = require('chai') 5 | const should = chai.should() 6 | 7 | chai.use(require('sinon-chai')) 8 | require('mocha-sinon') 9 | 10 | 11 | beforeEach(function() { 12 | this.sinon.stub(console, 'warn') 13 | }) 14 | 15 | 16 | describe('_heightWidthCalc', function() { 17 | 18 | describe('scaling with definite height and width', function() { 19 | 20 | it('should return original image height and width', function() { 21 | Blitz._heightWidthCalc(100, 200).should.deep.own.include({ height: 100, width: 200 }) 22 | }) 23 | 24 | it('should return "resizeTo" width and height', function() { 25 | Blitz._heightWidthCalc(100, 200, 101, 250).should.deep.own.include({ height: 101, width: 250 }) 26 | }) 27 | 28 | it('should return "resizeTo" width and height despite enforcing proportions', function() { 29 | Blitz._heightWidthCalc(100, 200, 101, 250, null, null, null, null, true).should.deep.own.include({ height: 101, width: 250 }) 30 | console.warn.calledWith('Warn: `proportional` is ignored when both `height` and `width` are provided.').should.be.true 31 | }) 32 | 33 | it('should return "resizeTo" width and height and ignore min/max height', function() { 34 | Blitz._heightWidthCalc(100, 200, 101, 250, 3, 4, 5, 6).should.deep.own.include({ height: 101, width: 250 }) 35 | console.warn.calledWith('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.').should.be.true 36 | }) 37 | 38 | it('should return "resizeTo" width and scaled height (maintaining proportions) and ignore min/max height', function() { 39 | Blitz._heightWidthCalc(100, 200, null, 250, 3, 4, 5, 6).should.deep.own.include({ height: 125, width: 250 }) 40 | console.warn.calledWith('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.').should.be.true 41 | }) 42 | }) 43 | 44 | describe('throw errors when max/min definitions are conflicting', function() { 45 | 46 | it('should throw error when minHeight > maxHeight', function() { 47 | (() => { 48 | Blitz._heightWidthCalc(100, 200, null, null, 100, null, 200) 49 | }).should.Throw(Error, '`minHeight` cannot be larger than `maxHeight.') 50 | }) 51 | 52 | it('should throw error when minWidth > maxWidth', function() { 53 | (() => { 54 | Blitz._heightWidthCalc(100, 200, null, null, null, 400, null, 401) 55 | }).should.Throw(Error, '`minWidth` cannot be larger than `maxWidth.') 56 | }) 57 | 58 | it('should throw error when both mins > both maxes', function() { 59 | (() => { 60 | Blitz._heightWidthCalc(100, 200, null, null, 100, 400, 101, 401) 61 | }).should.Throw(Error, '`minHeight` cannot be larger than `maxHeight. `minWidth` cannot be larger than `maxWidth.') 62 | }) 63 | 64 | }) 65 | 66 | describe('maxHeight and maxWidth scaling', function() { 67 | 68 | describe('proportional = false, without defined mins', function() { 69 | 70 | it('should return downscale to max height and width (smaller)', function() { 71 | Blitz._heightWidthCalc(100, 200, null, null, 50, 101, null, null, false).should.deep.own.include({ height: 50, width: 101 }) 72 | }) 73 | 74 | it('should return original height/width if maxes are large)', function() { 75 | Blitz._heightWidthCalc(100, 200, null, null, 101, 201, null, null, false).should.deep.own.include({ height: 100, width: 200 }) 76 | }) 77 | 78 | }) 79 | 80 | describe('proportional DOWN scaling, with defined max ONLY', function() { 81 | 82 | it('should return original height/width if maxes are large (proportional explicit true)', function() { 83 | Blitz._heightWidthCalc(100, 200, null, null, 101, 201, null, null, true).should.deep.own.include({ height: 100, width: 200 }) 84 | }) 85 | 86 | it('should return downscale, limited to maxHeight (limiting factor), where maxWidth provided', function() { 87 | Blitz._heightWidthCalc(100, 200, null, null, 50, 101, null, null).should.deep.own.include({ height: 50, width: 100 }) 88 | }) 89 | 90 | it('should return downscale, limited to maxWidth (limiting factor), where maxHeight provided', function() { 91 | Blitz._heightWidthCalc(100, 200, null, null, 51, 100, null, null).should.deep.own.include({ height: 50, width: 100 }) 92 | }) 93 | 94 | }) 95 | 96 | describe('proportional = false, with defined mins', function() { 97 | 98 | // not able to define conflicting mins due in-built safety, so no need to check 99 | it('should return downscale to max height and width (smaller), non-affecting defined mins', function() { 100 | Blitz._heightWidthCalc(100, 200, null, null, 50, 101, 49, 100, false).should.deep.own.include({ height: 50, width: 101 }) 101 | }) 102 | 103 | it('should return original height/width if maxes are large, non-affecting defined mins', function() { 104 | Blitz._heightWidthCalc(100, 200, null, null, 101, 201, 100, 200, false).should.deep.own.include({ height: 100, width: 200 }) 105 | }) 106 | 107 | }) 108 | 109 | describe('proportional DOWN scaling, non-affecting defined max and mins', function() { 110 | 111 | it('should return original height/width, proportional explicit true', function() { 112 | Blitz._heightWidthCalc(100, 200, null, null, 101, 201, 44, 66, true).should.deep.own.include({ height: 100, width: 200 }) 113 | }) 114 | 115 | it('should return original height/width, proportional explicit false', function() { 116 | Blitz._heightWidthCalc(100, 200, null, null, 101, 201, 44, 66, false).should.deep.own.include({ height: 100, width: 200 }) 117 | }) 118 | 119 | it('should return original height/width, proportional undefined (defaulted true)', function() { 120 | Blitz._heightWidthCalc(100, 200, null, null, 101, 201, 44, 66, false).should.deep.own.include({ height: 100, width: 200 }) 121 | }) 122 | 123 | }) 124 | 125 | describe('proportional DOWN scaling, with affecting defined max and mins', function() { 126 | 127 | it('should return downscale, limited to maxHeight (limiting factor), where maxWidth provided, non-affecting mins defined', function() { 128 | Blitz._heightWidthCalc(100, 200, null, null, 50, 101, 44, 60).should.deep.own.include({ height: 50, width: 100 }) 129 | }) 130 | 131 | it('should return downscale, limited to maxHeight (limiting factor), where maxWidth provided, limited to minWidth -- with no warnings', function() { 132 | Blitz._heightWidthCalc(100, 200, null, null, 50, 200, 44, 150).should.deep.own.include({ height: 50, width: 150 }) 133 | }) 134 | 135 | it('should return downscale, width of maxWidth (limiting), where maxHeight provided, height of minHeight (limiting) -- with no warnings', function() { 136 | Blitz._heightWidthCalc(100, 200, null, null, 83, 101, 55, 99).should.deep.own.include({ height: 55, width: 101 }) 137 | }) 138 | 139 | it('should return downscale, limited to maxHeight (limiting factor), where maxWidth provided, limited to minWidth, proportional explicit true -- with warnings', function() { 140 | Blitz._heightWidthCalc(100, 200, null, null, 50, 200, 44, 150, true).should.deep.own.include({ height: 50, width: 150 }) 141 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing minWidth of 150 as scaling image to maxHeight of 50 gives scaled width of 100.').should.be.true 142 | }) 143 | 144 | it('should return downscale, width of maxWidth (limiting), where maxHeight provided, height of minHeight (limiting), proportional explicit true -- with warnings', function() { 145 | Blitz._heightWidthCalc(100, 200, null, null, 83, 101, 55, 99, true).should.deep.own.include({ height: 55, width: 101 }) 146 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing minHeight of 55 as scaling image to maxWidth of 101 gives scaled height of 51.').should.be.true 147 | }) 148 | 149 | it('should return downscale, limited to maxHeight, non-affecting mins defined', function() { 150 | Blitz._heightWidthCalc(100, 200, null, null, 50, null, 44, 60).should.deep.own.include({ height: 50, width: 100 }) 151 | }) 152 | 153 | it('should return downscale, limited to maxHeight, limited to minWidth -- with no warnings', function() { 154 | Blitz._heightWidthCalc(100, 200, null, null, 50, null, 44, 150).should.deep.own.include({ height: 50, width: 150 }) 155 | }) 156 | 157 | it('should return downscale, width of maxWidth (limiting), height of minHeight (limiting) -- with no warnings', function() { 158 | Blitz._heightWidthCalc(100, 200, null, null, null, 101, 55, 99).should.deep.own.include({ height: 55, width: 101 }) 159 | }) 160 | 161 | it('should return downscale, limited to maxHeight (limiting factor), limited to minWidth, proportional explicit true -- with warnings', function() { 162 | Blitz._heightWidthCalc(100, 200, null, null, 50, null, 44, 150, true).should.deep.own.include({ height: 50, width: 150 }) 163 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing minWidth of 150 as scaling image to maxHeight of 50 gives scaled width of 100.').should.be.true 164 | }) 165 | 166 | it('should return downscale, width of maxWidth (limiting), height of minHeight (limiting), proportional explicit true -- with warnings', function() { 167 | Blitz._heightWidthCalc(100, 200, null, null, null, 101, 55, 99, true).should.deep.own.include({ height: 55, width: 101 }) 168 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing minHeight of 55 as scaling image to maxWidth of 101 gives scaled height of 51.').should.be.true 169 | }) 170 | }) 171 | 172 | describe('proportional UP scaling, with affecting defined max and mins', function() { 173 | 174 | it('should upscale, limited to minHeight (limiting factor), where minWidth provided, limited to maxWidth -- with no warnings', function() { 175 | Blitz._heightWidthCalc(100, 200, null, null, 400, 304, 203, 150).should.deep.own.include({ height: 203, width: 304 }) 176 | }) 177 | 178 | it('should upscale, width of minWidth (limiting), where minHeight provided, height of maxHeight (limiting) -- with no warnings', function() { 179 | Blitz._heightWidthCalc(100, 200, null, null, 83, 589, 55, 403).should.deep.own.include({ height: 83, width: 403 }) 180 | }) 181 | 182 | it('should upscale, limited to minHeight (limiting factor), where minWidth provided, limited to maxWidth, proportional explicit true -- with warnings', function() { 183 | Blitz._heightWidthCalc(100, 200, null, null, 245, 434, 245, 150, true).should.deep.own.include({ height: 245, width: 434 }) 184 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing maxWidth of 434 as scaling image to minHeight of 245 gives scaled width of 490.').should.be.true 185 | }) 186 | 187 | it('should upscale, width of minWidth (limiting), where minHeight provided, height of maxHeight (limiting), proportional explicit true -- with warnings', function() { 188 | Blitz._heightWidthCalc(100, 200, null, null, 101, 589, 55, 403, true).should.deep.own.include({ height: 101, width: 403 }) 189 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing maxHeight of 101 as scaling image to minWidth of 403 gives scaled height of 202.').should.be.true 190 | }) 191 | 192 | it('should upscale, limited to minHeight, non-affecting mins defined', function() { 193 | Blitz._heightWidthCalc(100, 200, null, null, 180, 400, 150, 60).should.deep.own.include({ height: 150, width: 300 }) 194 | }) 195 | 196 | it('should upscale, limited to minHeight, limited to maxWidth -- with no warnings', function() { 197 | Blitz._heightWidthCalc(100, 200, null, null, null, 202, 200, 150).should.deep.own.include({ height: 200, width: 202 }) 198 | }) 199 | 200 | it('should upscale, width of minWidth (limiting), height of maxHeight (limiting) -- with no warnings', function() { 201 | Blitz._heightWidthCalc(100, 200, null, null, 150, null, 55, 400).should.deep.own.include({ height: 150, width: 400 }) 202 | }) 203 | 204 | it('should upscale, limited to minHeight, limited to maxWidth, proportional explicit true -- with warning', function() { 205 | Blitz._heightWidthCalc(100, 200, null, null, null, 202, 200, 150, true).should.deep.own.include({ height: 200, width: 202 }) 206 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing maxWidth of 202 as scaling image to minHeight of 200 gives scaled width of 400.').should.be.true 207 | }) 208 | 209 | it('should upscale, width of minWidth (limiting), height of maxHeight (limiting), proportional explicit true -- with warning', function() { 210 | Blitz._heightWidthCalc(100, 200, null, null, 150, null, 55, 400, true).should.deep.own.include({ height: 150, width: 400 }) 211 | console.warn.calledWith('Warn: Unable to maintain image proportionality. Enforcing maxHeight of 150 as scaling image to minWidth of 400 gives scaled height of 200.').should.be.true 212 | }) 213 | }) 214 | }) 215 | }) -------------------------------------------------------------------------------- /src/blitz.js: -------------------------------------------------------------------------------- 1 | //name: Blitz Image Resizer 2 | //about: Fast, non-blocking client-side image resize/resample using Hermite filter with JavaScript. 3 | //author: calvintwr 4 | var Blitz = { 5 | resize: function(opts, callback) { 6 | 7 | if(typeof opts !== 'object') throw Error('You need to provide options for Blitz as a `object`.') 8 | 9 | var mandatory = ['source']; 10 | 11 | for(var i=0; i { 71 | this._output(event, canvas, opts, resizedDimens, startTime, callback) 72 | } 73 | worker.postMessage( 74 | [ 75 | original.data.buffer, 76 | originalWidth, 77 | originalHeight, 78 | resizeToWidth, 79 | resizeToHeight, 80 | resizedImage.data.buffer 81 | ], [ 82 | original.data.buffer, 83 | resizedImage.data.buffer 84 | ] 85 | ) 86 | } 87 | 88 | }, 89 | create: function(kind) { 90 | 91 | if (!kind || kind === 'promise') { 92 | var descendant = Object.create(this) 93 | var resize = descendant.resize.bind(descendant) 94 | 95 | function resizePromised(options) { 96 | return new Promise((resolve, reject) => { 97 | try { 98 | resize(options, output => { 99 | resolve(output) 100 | }) 101 | } catch (err) { 102 | reject(err) 103 | } 104 | }) 105 | } 106 | return resizePromised 107 | } 108 | 109 | if (kind === 'callback') { 110 | var descendant = Object.create(this) 111 | return descendant.resize.bind(descendant) 112 | } 113 | }, 114 | _output: function(event, canvas, opts, resizedDimens, startTime, callback){ 115 | let resizeToWidth = resizedDimens.width 116 | let resizeToHeight = resizedDimens.height 117 | 118 | if (event) { 119 | var resizedImage = new ImageData( 120 | new Uint8ClampedArray(event.data.data), 121 | resizeToWidth, 122 | resizeToHeight 123 | ) 124 | 125 | if (opts.logPerformance) console.log('Resize completed in ' + (Math.round(Date.now() - startTime)/1000) + 's'); 126 | 127 | canvas.getContext('2d').clearRect(0, 0, resizeToWidth, resizeToHeight); 128 | canvas.height = resizeToHeight; 129 | canvas.width = resizeToWidth; 130 | canvas.getContext('2d').putImageData(resizedImage, 0, 0); 131 | } 132 | 133 | var output; 134 | 135 | if (opts.output === 'canvas') { 136 | output = canvas 137 | } else if (['image', 'data'].indexOf(opts.output) > -1) { 138 | output = this._canvasToImageOrData(canvas, opts.mimeType, opts.quality, opts.output) 139 | } else if (opts.output === 'file') { 140 | output = this._canvasToFile(canvas, opts.mimeType, opts.quality, opts.output) 141 | } else if (opts.output === 'blob') { 142 | output = this._canvasToBlob(canvas, opts.mimeType, opts.quality, opts.output) 143 | } else if (opts.output === 'download') { 144 | output = this._download(this, canvas, opts) 145 | } else if (typeof opts.output === 'undefined') { 146 | // when not defined, assume whatever element type is the input, is the desired output 147 | var outputFormat; 148 | if(sourceElement.type === 'DATA') { 149 | outputFormat = 'data'; 150 | } else if(sourceElement.type === 'IMG') { 151 | outputFormat = 'image'; 152 | } 153 | if(outputFormat) { 154 | return output = this._canvasToImageOrData(canvas, opts.mimeType, opts.quality, outputFormat) 155 | } else { 156 | // else can only be canvas 157 | output = canvas; 158 | } 159 | 160 | } else { 161 | throw Error('`output` is not valid.'); 162 | } 163 | if (typeof callback === 'function') return callback(output) 164 | return output 165 | }, 166 | _heightWidthCalc: function(imgH, imgW, resizeToH, resizeToW, maxH, maxW, minH, minW, proportional) { 167 | 168 | // set to original image size. 169 | let resizeTo = { 170 | height: imgH, 171 | width: imgW 172 | } 173 | 174 | // if both height or width are supplied. 175 | if (resizeToH && resizeToW ) { 176 | // warnings if maxH or maxW were supplied 177 | if (maxH || maxW || minH || minH) console.warn('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.') 178 | if (proportional) console.warn('Warn: `proportional` is ignored when both `height` and `width` are provided.') 179 | return { 180 | height: resizeToH, 181 | width: resizeToW 182 | } 183 | } 184 | 185 | //pass this point, proportional is set to "defaulted true" 186 | //this differentiates from `true`, which is when it is defined by users. 187 | if (proportional === undefined || proportional === null) proportional = "defaulted true" 188 | 189 | // if width is supplied 190 | if (resizeToW) { 191 | if (maxH || maxW || minH || minH) console.warn('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.') 192 | return { 193 | width: resizeToW, 194 | height: proportional ? this._scale(imgW, imgH, resizeToW) : imgH 195 | } 196 | } 197 | 198 | // if height is supplied 199 | if (resizeToH) { 200 | if (maxH || maxW || minH || minH) console.warn('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.') 201 | return { 202 | height: resizeToH, 203 | width: proportional ? this._scale(imgH, imgW, resizeToH) : imgW 204 | } 205 | } 206 | 207 | let errors = [] 208 | if (minH && maxH && minH > maxH) errors.push('`minHeight` cannot be larger than `maxHeight.') 209 | if (minW && maxW && minW > maxW) errors.push('`minWidth` cannot be larger than `maxWidth.') 210 | if (errors.length > 0) throw Error(errors.join(' ')) 211 | 212 | // if both maxWidth and maxHeight are supplied 213 | if (maxW && maxH) { 214 | 215 | // if both maxH and maxW are smaller. 216 | if (maxW < imgW && maxH < imgH) { 217 | 218 | if (proportional === false) { 219 | return { 220 | width: maxW, 221 | height: maxH 222 | } 223 | } 224 | 225 | // scale using either and see which produces a smaller image 226 | let scaledbyMaxW = { 227 | width: maxW, 228 | height: this._scale(imgW, imgH, maxW) 229 | } 230 | 231 | let scaledbyMaxH = { 232 | width: this._scale(imgH, imgW, maxH), 233 | height: maxH 234 | } 235 | 236 | let maxWidthIsSmaller = (scaledbyMaxW.width * scaledbyMaxW.height) < (scaledbyMaxH.width * scaledbyMaxH.height) 237 | 238 | if (maxWidthIsSmaller) { 239 | // we want to check it against minH for diametrically opposed constrains. 240 | return _enforceMinMaxLayer(scaledbyMaxW, { minH }, proportional) 241 | } 242 | return _enforceMinMaxLayer(scaledbyMaxH, { minW }, proportional) 243 | } 244 | 245 | } 246 | 247 | // if ONLY maxWidth is supplied and it is smaller than imgW 248 | if (maxW && maxW < imgW) { 249 | let scaled = { 250 | width: maxW, 251 | height: proportional ? this._scale(imgW, imgH, maxW) : imgH 252 | } 253 | return _enforceMinMaxLayer(scaled, { minH }, proportional) 254 | } 255 | 256 | // if ONLY maxHeight is supplied and it is smaller than imgH 257 | if (maxH && maxH < imgH) { 258 | let scaled = { 259 | width: proportional ? this._scale(imgH, imgW, maxH) : imgW, 260 | height: maxH 261 | } 262 | return _enforceMinMaxLayer(scaled, { minW }, proportional) 263 | } 264 | 265 | // if both minWidth and minHeight are supplied 266 | if (minW && minH) { 267 | 268 | // if both minH and minW are larger. 269 | if (minW > imgW && minH > imgH) { 270 | 271 | if (proportional === false) { 272 | return { 273 | width: minW, 274 | height: minH 275 | } 276 | } 277 | 278 | // scale using either and see which produces a larger image 279 | let scaledbyMinW = { 280 | width: minW, 281 | height: this._scale(imgW, imgH, minW) 282 | } 283 | 284 | let scaledbyMinH = { 285 | width: this._scale(imgH, imgW, minH), 286 | height: minH 287 | } 288 | 289 | let minWidthIsLarger = (scaledbyMinW.width * scaledbyMinW.height) > (scaledbyMinH.width * scaledbyMinH.height) 290 | 291 | if (minWidthIsLarger) { 292 | // we want to check it against maxH for diametrically opposed constrains. 293 | return _enforceMinMaxLayer(scaledbyMinW, { maxH }, proportional) 294 | } 295 | return _enforceMinMaxLayer(scaledbyMinH, { maxW }, proportional) 296 | } 297 | 298 | } 299 | 300 | // if ONLY minWidth is supplied and it is larger than imgW 301 | if (minW && minW > imgW) { 302 | let scaled = { 303 | width: minW, 304 | height: proportional ? this._scale(imgW, imgH, minW) : imgH 305 | } 306 | return _enforceMinMaxLayer(scaled, { maxH }, proportional) 307 | } 308 | 309 | // if ONLY minHeight is supplied and it is larger than imgH 310 | if (minH && minH > imgH) { 311 | let scaled = { 312 | width: proportional ? this._scale(imgH, imgW, minH) : imgW, 313 | height: minH 314 | } 315 | return _enforceMinMaxLayer(scaled, { maxW }, proportional) 316 | } 317 | 318 | // nothing is the limiting factor, or nothing is suppled 319 | return resizeTo 320 | 321 | // if user wants it to be proportional 322 | // but also defined potentially diametrically opposing min/max 323 | function _enforceMinMaxLayer(scaled, minMax, proportional) { 324 | 325 | let { minH, minW, maxH, maxW } = minMax 326 | 327 | // we only warn if user deliberately sets proportional to true. 328 | let warn = 'Warn: Unable to maintain image proportionality.' 329 | 330 | // when scaled by maxWidth, if image height is smaller than minHeight 331 | if (minH) { 332 | if (scaled.height < minH) { 333 | if (proportional === true) console.warn(`${warn} Enforcing minHeight of ${minH} as scaling image to maxWidth of ${scaled.width} gives scaled height of ${scaled.height}.`) 334 | return { 335 | height: minH, 336 | width: scaled.width 337 | } 338 | } 339 | return scaled 340 | } 341 | 342 | // when scaled by maxHeight, if image width is smaller than minWidth 343 | if (minW) { 344 | if (scaled.width < minW) { 345 | if (proportional === true) console.warn(`${warn} Enforcing minWidth of ${minW} as scaling image to maxHeight of ${scaled.height} gives scaled width of ${scaled.width}.`) 346 | return { 347 | height: scaled.height, 348 | width: minW 349 | } 350 | } 351 | return scaled 352 | } 353 | 354 | // when scaled by minWidth, if image height is larger than maxHeight 355 | if (maxH) { 356 | if (scaled.height > maxH) { 357 | if (proportional === true) console.warn(`${warn} Enforcing maxHeight of ${maxH} as scaling image to minWidth of ${scaled.width} gives scaled height of ${scaled.height}.`) 358 | return { 359 | height: maxH, 360 | width: scaled.width 361 | } 362 | } 363 | return scaled 364 | } 365 | 366 | // when scaled by minHeight, if image width is larger than maxWidth 367 | if (maxW) { 368 | if (scaled.width > maxW) { 369 | if (proportional === true) console.warn(`${warn} Enforcing maxWidth of ${maxW} as scaling image to minHeight of ${scaled.height} gives scaled width of ${scaled.width}.`) 370 | return { 371 | height: scaled.height, 372 | width: maxW 373 | } 374 | } 375 | return scaled 376 | } 377 | return scaled 378 | } 379 | 380 | }, 381 | _scale: function(dimen1, dimen2, resizedDimen1) { 382 | return Math.round( resizedDimen1 / (dimen1/dimen2) ) 383 | }, 384 | _extract: function(source) { 385 | if (source instanceof File) { 386 | // File 387 | return { 388 | source: source, 389 | type: 'FILE' 390 | } 391 | } else if (typeof source === 'string' && source.indexOf('data') === 0) { 392 | // dataBlob 393 | return { 394 | source: source, 395 | type: 'DATA' 396 | } 397 | } else if (source.tagName) { 398 | // getElementById sources will pass this 399 | return { 400 | source: source, 401 | type: source.tagName 402 | } 403 | } else if (source[0].tagName) { 404 | return { 405 | source: source[0], 406 | type: source[0].tagName 407 | } 408 | } 409 | 410 | throw Error('`source` element to be invalid.'); 411 | }, 412 | _imageToCanvas: function(image) { 413 | // create a off-screen canvas 414 | var c = document.createElement('canvas'); 415 | var context = c.getContext('2d'); 416 | c.height = image.height; 417 | c.width = image.width; 418 | context.drawImage(image, 0, 0, image.width, image.height); 419 | return c; 420 | }, 421 | _dataToCanvas: function(data, opts, callback) { 422 | 423 | // create an off-screen image 424 | var image = new Image(); 425 | 426 | image.src = data; 427 | image.onload = () => { 428 | // create an off-screen canvas 429 | var c = document.createElement('canvas'); 430 | var context = c.getContext('2d'); 431 | 432 | c.height = image.height; 433 | c.width = image.width; 434 | context.drawImage(image, 0, 0, image.width, image.height); 435 | 436 | opts.output = opts.output ? opts.output : 'data'; 437 | opts.source = c; 438 | 439 | this.resize(opts, callback); 440 | } 441 | 442 | }, 443 | _fileToCanvas: function(file, opts, callback) { 444 | var reader = new FileReader(); 445 | reader.onload = event => { 446 | opts.source = event.target.result 447 | if (!opts.output) opts.output = 'file' 448 | this.resize(opts, callback) 449 | } 450 | reader.readAsDataURL(file); 451 | }, 452 | _canvasToImageOrData: function(canvas, mimeType, quality, output) { 453 | var data = canvas.toDataURL(mimeType, quality); 454 | var image; 455 | if (output === 'data') { 456 | image = data 457 | } else { 458 | var image = new Image(); 459 | image.src = canvas.toDataURL(mimeType, quality); 460 | } 461 | return image; 462 | }, 463 | _canvasToBlob: function(canvas, mimeType, quality, output) { 464 | // safari does not support canvas #toBlob, so need to convert from dataURI 465 | var data = this._canvasToImageOrData(canvas, mimeType, quality, 'data') 466 | var blob = this._dataURItoBlob(data) 467 | return blob 468 | }, 469 | _canvasToFile: function(canvas, mimeType, quality, output) { 470 | var blob = this._canvasToBlob(canvas, mimeType, quality) 471 | return new File([blob], 'resized', { type: mimeType, lastModified: Date.now() }) 472 | }, 473 | _dataURItoBlob: function(dataURI) { 474 | // convert base64/URLEncoded data component to raw binary data held in a string 475 | var byteString; 476 | if (dataURI.split(',')[0].indexOf('base64') >= 0) { 477 | byteString = atob(dataURI.split(',')[1]); 478 | } else { 479 | byteString = unescape(dataURI.split(',')[1]); 480 | } 481 | // separate out the mime component 482 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 483 | 484 | // write the bytes of the string to a typed array 485 | var ia = new Uint8Array(byteString.length); 486 | for (var i = 0; i < byteString.length; i++) { 487 | ia[i] = byteString.charCodeAt(i); 488 | } 489 | return new Blob([ia], {type:mimeString}); 490 | }, 491 | _mimeConverter: function(format, reversed) { 492 | 493 | // if undefined, assume no compression. 494 | if (typeof format === 'undefined') return 'image/png'; 495 | 496 | var formats = [ 497 | 'raw', 498 | 'png', 499 | 'jpg', 500 | 'gif', 501 | 'image/raw', 502 | 'image/png', 503 | 'image/jpeg', 504 | 'image/gif', 505 | ]; 506 | 507 | var index = formats.indexOf(format); 508 | 509 | if (index === -1) throw Error('mimeType can only be `raw`, `png`, `jpg` or `gif`'); 510 | 511 | if (index === 0 || index === 1) return 'image/png'; 512 | if (index === 2) return 'image/jpeg'; 513 | if (index === 3) return 'image/gif'; 514 | 515 | return format 516 | }, 517 | _download: function(self, canvas, opts) { 518 | return function() { 519 | var link = document.createElement('a'); 520 | link.href = self._canvasToImageOrData(canvas, opts.mimeType, opts.quality, 'data'); 521 | link.download = opts.mimeType === 'image/jpeg' ? 'resized.jpg' : 'resized'; 522 | document.body.appendChild(link); 523 | link.click(); 524 | document.body.removeChild(link); 525 | } 526 | }, 527 | _workerBlobURL: function(filter) { 528 | 529 | if (filter) filter = this['_filter_' + filter] 530 | if (!filter) filter = this['_filter_hermite'] 531 | 532 | var _workerTaskString = this._workerTaskString(filter) 533 | 534 | return window.URL.createObjectURL(new Blob( 535 | [ _workerTaskString ], 536 | {type: 'application/javascript'} 537 | )) 538 | }, 539 | _workerTaskString: function(filter) { 540 | var task = '' 541 | task += '(' 542 | task += 'function () {'; 543 | task += ' onmessage = function (event) {'; 544 | task += ' var filter = '; 545 | task += filter.toString(); 546 | task += ' ;var resized = filter(event)'; 547 | task += ' ;postMessage({data: resized }, [ resized ]);'; 548 | task += ' };'; 549 | task += '}'; 550 | task += ')()' 551 | return task 552 | }, 553 | addFilter: function(filter) { 554 | let name = `_filter_${filter.name}` 555 | if (this[name]) throw Error(`Filter name already exist for ${filter.name}. Please use another name.`) 556 | this[name] = filter.filter 557 | return this 558 | } 559 | }; 560 | 561 | module.exports = Blitz 562 | exports = module.exports 563 | -------------------------------------------------------------------------------- /dist/blitz.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Blitz = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i -1) { 116 | output = this._canvasToImageOrData(canvas, opts.mimeType, opts.quality, opts.output); 117 | } else if (opts.output === 'file') { 118 | output = this._canvasToFile(canvas, opts.mimeType, opts.quality, opts.output); 119 | } else if (opts.output === 'blob') { 120 | output = this._canvasToBlob(canvas, opts.mimeType, opts.quality, opts.output); 121 | } else if (opts.output === 'download') { 122 | output = this._download(this, canvas, opts); 123 | } else if (typeof opts.output === 'undefined') { 124 | // when not defined, assume whatever element type is the input, is the desired output 125 | var outputFormat; 126 | 127 | if (sourceElement.type === 'DATA') { 128 | outputFormat = 'data'; 129 | } else if (sourceElement.type === 'IMG') { 130 | outputFormat = 'image'; 131 | } 132 | 133 | if (outputFormat) { 134 | return output = this._canvasToImageOrData(canvas, opts.mimeType, opts.quality, outputFormat); 135 | } else { 136 | // else can only be canvas 137 | output = canvas; 138 | } 139 | } else { 140 | throw Error('`output` is not valid.'); 141 | } 142 | 143 | if (typeof callback === 'function') return callback(output); 144 | return output; 145 | }, 146 | _heightWidthCalc: function _heightWidthCalc(imgH, imgW, resizeToH, resizeToW, maxH, maxW, minH, minW, proportional) { 147 | // set to original image size. 148 | var resizeTo = { 149 | height: imgH, 150 | width: imgW 151 | }; // if both height or width are supplied. 152 | 153 | if (resizeToH && resizeToW) { 154 | // warnings if maxH or maxW were supplied 155 | if (maxH || maxW || minH || minH) console.warn('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.'); 156 | if (proportional) console.warn('Warn: `proportional` is ignored when both `height` and `width` are provided.'); 157 | return { 158 | height: resizeToH, 159 | width: resizeToW 160 | }; 161 | } //pass this point, proportional is set to "defaulted true" 162 | //this differentiates from `true`, which is when it is defined by users. 163 | 164 | 165 | if (proportional === undefined || proportional === null) proportional = "defaulted true"; // if width is supplied 166 | 167 | if (resizeToW) { 168 | if (maxH || maxW || minH || minH) console.warn('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.'); 169 | return { 170 | width: resizeToW, 171 | height: proportional ? this._scale(imgW, imgH, resizeToW) : imgH 172 | }; 173 | } // if height is supplied 174 | 175 | 176 | if (resizeToH) { 177 | if (maxH || maxW || minH || minH) console.warn('Warn: `min/maxHeight` or `min/maxWidth` are ignored when `height` and/or `width` are supplied.'); 178 | return { 179 | height: resizeToH, 180 | width: proportional ? this._scale(imgH, imgW, resizeToH) : imgW 181 | }; 182 | } 183 | 184 | var errors = []; 185 | if (minH && maxH && minH > maxH) errors.push('`minHeight` cannot be larger than `maxHeight.'); 186 | if (minW && maxW && minW > maxW) errors.push('`minWidth` cannot be larger than `maxWidth.'); 187 | if (errors.length > 0) throw Error(errors.join(' ')); // if both maxWidth and maxHeight are supplied 188 | 189 | if (maxW && maxH) { 190 | // if both maxH and maxW are smaller. 191 | if (maxW < imgW && maxH < imgH) { 192 | if (proportional === false) { 193 | return { 194 | width: maxW, 195 | height: maxH 196 | }; 197 | } // scale using either and see which produces a smaller image 198 | 199 | 200 | var scaledbyMaxW = { 201 | width: maxW, 202 | height: this._scale(imgW, imgH, maxW) 203 | }; 204 | var scaledbyMaxH = { 205 | width: this._scale(imgH, imgW, maxH), 206 | height: maxH 207 | }; 208 | var maxWidthIsSmaller = scaledbyMaxW.width * scaledbyMaxW.height < scaledbyMaxH.width * scaledbyMaxH.height; 209 | 210 | if (maxWidthIsSmaller) { 211 | // we want to check it against minH for diametrically opposed constrains. 212 | return _enforceMinMaxLayer(scaledbyMaxW, { 213 | minH: minH 214 | }, proportional); 215 | } 216 | 217 | return _enforceMinMaxLayer(scaledbyMaxH, { 218 | minW: minW 219 | }, proportional); 220 | } 221 | } // if ONLY maxWidth is supplied and it is smaller than imgW 222 | 223 | 224 | if (maxW && maxW < imgW) { 225 | var scaled = { 226 | width: maxW, 227 | height: proportional ? this._scale(imgW, imgH, maxW) : imgH 228 | }; 229 | return _enforceMinMaxLayer(scaled, { 230 | minH: minH 231 | }, proportional); 232 | } // if ONLY maxHeight is supplied and it is smaller than imgH 233 | 234 | 235 | if (maxH && maxH < imgH) { 236 | var _scaled = { 237 | width: proportional ? this._scale(imgH, imgW, maxH) : imgW, 238 | height: maxH 239 | }; 240 | return _enforceMinMaxLayer(_scaled, { 241 | minW: minW 242 | }, proportional); 243 | } // if both minWidth and minHeight are supplied 244 | 245 | 246 | if (minW && minH) { 247 | // if both minH and minW are larger. 248 | if (minW > imgW && minH > imgH) { 249 | if (proportional === false) { 250 | return { 251 | width: minW, 252 | height: minH 253 | }; 254 | } // scale using either and see which produces a larger image 255 | 256 | 257 | var scaledbyMinW = { 258 | width: minW, 259 | height: this._scale(imgW, imgH, minW) 260 | }; 261 | var scaledbyMinH = { 262 | width: this._scale(imgH, imgW, minH), 263 | height: minH 264 | }; 265 | var minWidthIsLarger = scaledbyMinW.width * scaledbyMinW.height > scaledbyMinH.width * scaledbyMinH.height; 266 | 267 | if (minWidthIsLarger) { 268 | // we want to check it against maxH for diametrically opposed constrains. 269 | return _enforceMinMaxLayer(scaledbyMinW, { 270 | maxH: maxH 271 | }, proportional); 272 | } 273 | 274 | return _enforceMinMaxLayer(scaledbyMinH, { 275 | maxW: maxW 276 | }, proportional); 277 | } 278 | } // if ONLY minWidth is supplied and it is larger than imgW 279 | 280 | 281 | if (minW && minW > imgW) { 282 | var _scaled2 = { 283 | width: minW, 284 | height: proportional ? this._scale(imgW, imgH, minW) : imgH 285 | }; 286 | return _enforceMinMaxLayer(_scaled2, { 287 | maxH: maxH 288 | }, proportional); 289 | } // if ONLY minHeight is supplied and it is larger than imgH 290 | 291 | 292 | if (minH && minH > imgH) { 293 | var _scaled3 = { 294 | width: proportional ? this._scale(imgH, imgW, minH) : imgW, 295 | height: minH 296 | }; 297 | return _enforceMinMaxLayer(_scaled3, { 298 | maxW: maxW 299 | }, proportional); 300 | } // nothing is the limiting factor, or nothing is suppled 301 | 302 | 303 | return resizeTo; // if user wants it to be proportional 304 | // but also defined potentially diametrically opposing min/max 305 | 306 | function _enforceMinMaxLayer(scaled, minMax, proportional) { 307 | var minH = minMax.minH, 308 | minW = minMax.minW, 309 | maxH = minMax.maxH, 310 | maxW = minMax.maxW; // we only warn if user deliberately sets proportional to true. 311 | 312 | var warn = 'Warn: Unable to maintain image proportionality.'; // when scaled by maxWidth, if image height is smaller than minHeight 313 | 314 | if (minH) { 315 | if (scaled.height < minH) { 316 | if (proportional === true) console.warn("".concat(warn, " Enforcing minHeight of ").concat(minH, " as scaling image to maxWidth of ").concat(scaled.width, " gives scaled height of ").concat(scaled.height, ".")); 317 | return { 318 | height: minH, 319 | width: scaled.width 320 | }; 321 | } 322 | 323 | return scaled; 324 | } // when scaled by maxHeight, if image width is smaller than minWidth 325 | 326 | 327 | if (minW) { 328 | if (scaled.width < minW) { 329 | if (proportional === true) console.warn("".concat(warn, " Enforcing minWidth of ").concat(minW, " as scaling image to maxHeight of ").concat(scaled.height, " gives scaled width of ").concat(scaled.width, ".")); 330 | return { 331 | height: scaled.height, 332 | width: minW 333 | }; 334 | } 335 | 336 | return scaled; 337 | } // when scaled by minWidth, if image height is larger than maxHeight 338 | 339 | 340 | if (maxH) { 341 | if (scaled.height > maxH) { 342 | if (proportional === true) console.warn("".concat(warn, " Enforcing maxHeight of ").concat(maxH, " as scaling image to minWidth of ").concat(scaled.width, " gives scaled height of ").concat(scaled.height, ".")); 343 | return { 344 | height: maxH, 345 | width: scaled.width 346 | }; 347 | } 348 | 349 | return scaled; 350 | } // when scaled by minHeight, if image width is larger than maxWidth 351 | 352 | 353 | if (maxW) { 354 | if (scaled.width > maxW) { 355 | if (proportional === true) console.warn("".concat(warn, " Enforcing maxWidth of ").concat(maxW, " as scaling image to minHeight of ").concat(scaled.height, " gives scaled width of ").concat(scaled.width, ".")); 356 | return { 357 | height: scaled.height, 358 | width: maxW 359 | }; 360 | } 361 | 362 | return scaled; 363 | } 364 | 365 | return scaled; 366 | } 367 | }, 368 | _scale: function _scale(dimen1, dimen2, resizedDimen1) { 369 | return Math.round(resizedDimen1 / (dimen1 / dimen2)); 370 | }, 371 | _extract: function _extract(source) { 372 | if (source instanceof File) { 373 | // File 374 | return { 375 | source: source, 376 | type: 'FILE' 377 | }; 378 | } else if (typeof source === 'string' && source.indexOf('data') === 0) { 379 | // dataBlob 380 | return { 381 | source: source, 382 | type: 'DATA' 383 | }; 384 | } else if (source.tagName) { 385 | // getElementById sources will pass this 386 | return { 387 | source: source, 388 | type: source.tagName 389 | }; 390 | } else if (source[0].tagName) { 391 | return { 392 | source: source[0], 393 | type: source[0].tagName 394 | }; 395 | } 396 | 397 | throw Error('`source` element to be invalid.'); 398 | }, 399 | _imageToCanvas: function _imageToCanvas(image) { 400 | // create a off-screen canvas 401 | var c = document.createElement('canvas'); 402 | var context = c.getContext('2d'); 403 | c.height = image.height; 404 | c.width = image.width; 405 | context.drawImage(image, 0, 0, image.width, image.height); 406 | return c; 407 | }, 408 | _dataToCanvas: function _dataToCanvas(data, opts, callback) { 409 | var _this2 = this; 410 | 411 | // create an off-screen image 412 | var image = new Image(); 413 | image.src = data; 414 | 415 | image.onload = function () { 416 | // create an off-screen canvas 417 | var c = document.createElement('canvas'); 418 | var context = c.getContext('2d'); 419 | c.height = image.height; 420 | c.width = image.width; 421 | context.drawImage(image, 0, 0, image.width, image.height); 422 | opts.output = opts.output ? opts.output : 'data'; 423 | opts.source = c; 424 | 425 | _this2.resize(opts, callback); 426 | }; 427 | }, 428 | _fileToCanvas: function _fileToCanvas(file, opts, callback) { 429 | var _this3 = this; 430 | 431 | var reader = new FileReader(); 432 | 433 | reader.onload = function (event) { 434 | opts.source = event.target.result; 435 | if (!opts.output) opts.output = 'file'; 436 | 437 | _this3.resize(opts, callback); 438 | }; 439 | 440 | reader.readAsDataURL(file); 441 | }, 442 | _canvasToImageOrData: function _canvasToImageOrData(canvas, mimeType, quality, output) { 443 | var data = canvas.toDataURL(mimeType, quality); 444 | var image; 445 | 446 | if (output === 'data') { 447 | image = data; 448 | } else { 449 | var image = new Image(); 450 | image.src = canvas.toDataURL(mimeType, quality); 451 | } 452 | 453 | return image; 454 | }, 455 | _canvasToBlob: function _canvasToBlob(canvas, mimeType, quality, output) { 456 | // safari does not support canvas #toBlob, so need to convert from dataURI 457 | var data = this._canvasToImageOrData(canvas, mimeType, quality, 'data'); 458 | 459 | var blob = this._dataURItoBlob(data); 460 | 461 | return blob; 462 | }, 463 | _canvasToFile: function _canvasToFile(canvas, mimeType, quality, output) { 464 | var blob = this._canvasToBlob(canvas, mimeType, quality); 465 | 466 | return new File([blob], 'resized', { 467 | type: mimeType, 468 | lastModified: Date.now() 469 | }); 470 | }, 471 | _dataURItoBlob: function _dataURItoBlob(dataURI) { 472 | // convert base64/URLEncoded data component to raw binary data held in a string 473 | var byteString; 474 | 475 | if (dataURI.split(',')[0].indexOf('base64') >= 0) { 476 | byteString = atob(dataURI.split(',')[1]); 477 | } else { 478 | byteString = unescape(dataURI.split(',')[1]); 479 | } // separate out the mime component 480 | 481 | 482 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to a typed array 483 | 484 | var ia = new Uint8Array(byteString.length); 485 | 486 | for (var i = 0; i < byteString.length; i++) { 487 | ia[i] = byteString.charCodeAt(i); 488 | } 489 | 490 | return new Blob([ia], { 491 | type: mimeString 492 | }); 493 | }, 494 | _mimeConverter: function _mimeConverter(format, reversed) { 495 | // if undefined, assume no compression. 496 | if (typeof format === 'undefined') return 'image/png'; 497 | var formats = ['raw', 'png', 'jpg', 'gif', 'image/raw', 'image/png', 'image/jpeg', 'image/gif']; 498 | var index = formats.indexOf(format); 499 | if (index === -1) throw Error('mimeType can only be `raw`, `png`, `jpg` or `gif`'); 500 | if (index === 0 || index === 1) return 'image/png'; 501 | if (index === 2) return 'image/jpeg'; 502 | if (index === 3) return 'image/gif'; 503 | return format; 504 | }, 505 | _download: function _download(self, canvas, opts) { 506 | return function () { 507 | var link = document.createElement('a'); 508 | link.href = self._canvasToImageOrData(canvas, opts.mimeType, opts.quality, 'data'); 509 | link.download = opts.mimeType === 'image/jpeg' ? 'resized.jpg' : 'resized'; 510 | document.body.appendChild(link); 511 | link.click(); 512 | document.body.removeChild(link); 513 | }; 514 | }, 515 | _workerBlobURL: function _workerBlobURL(filter) { 516 | if (filter) filter = this['_filter_' + filter]; 517 | if (!filter) filter = this['_filter_hermite']; 518 | 519 | var _workerTaskString = this._workerTaskString(filter); 520 | 521 | return window.URL.createObjectURL(new Blob([_workerTaskString], { 522 | type: 'application/javascript' 523 | })); 524 | }, 525 | _workerTaskString: function _workerTaskString(filter) { 526 | var task = ''; 527 | task += '('; 528 | task += 'function () {'; 529 | task += ' onmessage = function (event) {'; 530 | task += ' var filter = '; 531 | task += filter.toString(); 532 | task += ' ;var resized = filter(event)'; 533 | task += ' ;postMessage({data: resized }, [ resized ]);'; 534 | task += ' };'; 535 | task += '}'; 536 | task += ')()'; 537 | return task; 538 | }, 539 | addFilter: function addFilter(filter) { 540 | var name = "_filter_".concat(filter.name); 541 | if (this[name]) throw Error("Filter name already exist for ".concat(filter.name, ". Please use another name.")); 542 | this[name] = filter.filter; 543 | return this; 544 | } 545 | }; 546 | module.exports = Blitz; 547 | exports = module.exports; 548 | 549 | },{}],2:[function(require,module,exports){ 550 | 'use strict'; 551 | 552 | var name = 'hermite'; 553 | 554 | var filter = function filter(event) { 555 | var data = new Uint8ClampedArray(event.data[0]); 556 | var W = event.data[1]; 557 | var H = event.data[2]; 558 | var img = new ImageData(data, W, H); 559 | var data2 = new Uint8ClampedArray(event.data[5]); 560 | var W2 = event.data[3]; 561 | var H2 = event.data[4]; 562 | var img2 = new ImageData(data2, W2, H2); 563 | var ratio_w = W / W2; 564 | var ratio_h = H / H2; 565 | var ratio_w_half = Math.ceil(ratio_w / 2); 566 | var ratio_h_half = Math.ceil(ratio_h / 2); 567 | 568 | for (var j = 0; j < H2; j++) { 569 | for (var i = 0; i < W2; i++) { 570 | var x2 = (i + j * W2) * 4; 571 | var weight = 0; 572 | var weights = 0; 573 | var gx_r = gx_g = gx_b = gx_a = 0; 574 | var center_y = (j + 0.5) * ratio_h; 575 | 576 | for (var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) { 577 | var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half; 578 | var center_x = (i + 0.5) * ratio_w; 579 | var w0 = dy * dy; //pre-calc part of w 580 | 581 | for (var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) { 582 | var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half; 583 | var w = Math.sqrt(w0 + dx * dx); 584 | 585 | if (w >= -1 && w <= 1) { 586 | //hermite filter 587 | weight = 2 * w * w * w - 3 * w * w + 1; 588 | 589 | if (weight > 0) { 590 | dx = 4 * (xx + yy * W); 591 | gx_r += weight * data[dx]; 592 | gx_g += weight * data[dx + 1]; 593 | gx_b += weight * data[dx + 2]; 594 | gx_a += weight * data[dx + 3]; 595 | weights += weight; 596 | } 597 | } 598 | } 599 | } 600 | 601 | data2[x2] = gx_r / weights; 602 | data2[x2 + 1] = gx_g / weights; 603 | data2[x2 + 2] = gx_b / weights; 604 | data2[x2 + 3] = gx_a / weights; 605 | } 606 | } 607 | 608 | return img2.data.buffer; 609 | }; 610 | 611 | module.exports = { 612 | name: name, 613 | filter: filter 614 | }; 615 | 616 | },{}],3:[function(require,module,exports){ 617 | 'use strict'; 618 | 619 | var Blitz = require('./blitz.js'); 620 | 621 | Blitz.addFilter(require('./filters/hermite.js')); 622 | module.exports = Object.create(Blitz); 623 | exports = module.exports; 624 | 625 | },{"./blitz.js":1,"./filters/hermite.js":2}]},{},[3])(3) 626 | }); 627 | --------------------------------------------------------------------------------