├── 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 | 
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 |
--------------------------------------------------------------------------------