├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── lib ├── bitmap.js ├── brush.js ├── enums.js ├── graphics.js ├── resize.js └── utils.js ├── package.json ├── spec ├── blur.spec.js ├── resize.spec.js └── rotate.spec.js └── test ├── dimensions.js ├── invert.js ├── resize.js └── rotate.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Test Output Folder 11 | out/* 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 30 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 guyonroche 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageJS 2 | 3 | A Pure JavaScript Image manipulation library. 4 | Read and write JPG and PNG image files or streams and perform a number of operations on them. 5 | 6 | 7 | # Installation 8 | 9 | npm install imagejs 10 | 11 | # New Features! 12 | 13 | 16 | 17 | # Backlog 18 | 19 | 22 | 23 | # Contents 24 | 25 | 47 | 48 | 49 | 50 | # Interface 51 | 52 | ```javascript 53 | var ImageJS = require("imagejs"); 54 | ``` 55 | 56 | ## Creating Bitmaps 57 | 58 | ```javascript 59 | 60 | // Create an uninitialized bitmap 320x200 61 | // Note: the bitmap may be filled with random data 62 | var bitmap = new ImageJS.Bitmap({width: 320, height: 200}); 63 | 64 | // Create a bitmap filled with green 65 | var greenBitmap = new ImageJS.Bitmap({ 66 | width: 100, height: 100, 67 | color: {r: 0, g: 255, b: 0, a: 255 68 | }); 69 | 70 | // Copy a bitmap 71 | var copy = new ImageJS.Bitmap(otherBitmap); 72 | 73 | // Create a bitmap and attach to supplied data structure 74 | var attachedBitmap = new ImageJS.Bitmap({ 75 | width: 100, 76 | height: 100, 77 | data: new Buffer(4 * 100 * 100) 78 | }); 79 | 80 | // Create an empty (null) bitmap, ready for reading 81 | // from file or stream 82 | var nullBitmap = new ImageJS.Bitmap(); 83 | 84 | ``` 85 | 86 | ## Manipulating Bitmaps 87 | 88 | ### Set Pixel 89 | 90 | ```javascript 91 | // Set a pixel 92 | // where: 0 <= x < width, 0 <= y < height, 0 <= r,g,b,a < 256 93 | bitmap.setPixel(x,y, r,g,b,a); 94 | 95 | // Set a pixesl using a color object 96 | var yellow = {r:255, g:255, b:0}; // alpha defaults to 255 97 | bitmap.setPixel(x,y, yellow); 98 | ``` 99 | 100 | ### Get Pixel 101 | 102 | ```javascript 103 | // fetch the color of a pixel 104 | var color = bitmap.getPixel(x,y); 105 | 106 | // to improve performance you can supply the color object 107 | var color = {}; 108 | color = bitmap.getPixel(x,y, color); 109 | ``` 110 | 111 | ### Negative 112 | ```javascript 113 | // Create a new bitmap that is a negative of the original 114 | var negative = bitmap.negative(); 115 | ``` 116 | 117 | ### Blur 118 | ```javascript 119 | // blur with simple gaussian filter 120 | var blurred = bitmap.blur(); 121 | ``` 122 | 123 | ### Crop 124 | ```javascript 125 | // create a new bitmap from a portion of another 126 | var cropped = bitmap.crop({top: 50, left: 30, width: 100, height: 100}); 127 | ``` 128 | 129 | ### Resize 130 | ```javascript 131 | // resize to 64x64 icon sized bitmap using nearest neighbor algorithm & stretch to fit 132 | var thumbnail = bitmap.resize({ 133 | width: 64, height: 64, 134 | algorithm: "nearestNeighbor" 135 | }); 136 | 137 | // resize to 100x150 bitmap using bilinear interpolation and cropping to fit, 138 | // gravity center 139 | var thumbnail = bitmap.resize({ 140 | width: 100, height: 150, 141 | algorithm: "bilinearInterpolation", 142 | fit: "crop", 143 | gravity: {x:0.5, y:0.5} // center - note: this is the default 144 | }); 145 | 146 | // resize to 300x200 bitmap using bicubic interpolation and padding to fit, 147 | // pad color solid red 148 | var thumbnail = bitmap.resize({ 149 | width: 300, height: 200, 150 | algorithm: "bicubicInterpolation", 151 | fit: "pad", 152 | padColor: {r:255, g:0, b:0, a:255} 153 | }); 154 | 155 | ``` 156 | 157 | **Supported Resize Algorithms** 158 | * nearestNeighbor 159 | * bilinearInterpolation 160 | * bicubicInterpolation 161 | * hermiteInterpolation 162 | * bezierInterpolation 163 | 164 | ### Rotate 165 | ```javascript 166 | // rotate image 0.5 radians counterclockwise, keeping the dimensions the same 167 | // and padding with red 168 | // Note: default fit is "same" so including it in options is optional 169 | var red = {r: 255, g: 0, b: 0, a: 255}; 170 | var rotated = bitmap.rotate({radians: 0.5, fit: "same", padColor: red}); 171 | 172 | // rotate image 10 degrees clockwise, preserving entire image and padding with 173 | / transparent white 174 | var transparentWhite = {r: 255, g: 255, b: 255, a: 0}; 175 | var rotated = bitmap.rotate({degrees: -10, fit: "pad", padColor: transparentWhite}); 176 | 177 | // rotate image 45 degress counterclockwise, cropping so all of the result 178 | // image comes from the source. 179 | var rotated = bitmap.rotate({degrees: 45, fit: "crop"}); 180 | 181 | // rotate image 30 degrees counterclockwise, selecting custom dimensions. 182 | // Note: image will not be scaled. 183 | // default padColor (if required) is transparentBlack. 184 | var rotated = bitmap.rotate({degrees: 30, fit: "custom", width: 100, height: 150}); 185 | 186 | ``` 187 | 188 | ## Reading Images 189 | 190 | ```javascript 191 | // read from a file 192 | var bitmap = new Bitmap(); 193 | bitmap.readFile(filename) 194 | .then(function() { 195 | // bitmap is ready 196 | }); 197 | 198 | // read JPG data from stream 199 | var stream = createReadStream(); 200 | var bitmap = new Bitmap(); 201 | bitmap.read(stream, { type: ImageJS.ImageType.JPG }) 202 | .then(function() { 203 | // bitmap is ready 204 | }); 205 | 206 | ``` 207 | 208 | ## Writing Images 209 | 210 | ```javascript 211 | // write to a jpg file, quality 75 (default is 90) 212 | return bitmap.writeFile("image.jpg", { quality:75 }) 213 | .then(function() { 214 | // bitmap has been saved 215 | }); 216 | 217 | // write PNG Image to a stream 218 | var stream = createWriteStream(); 219 | return bitmap.write(stream, {type: ImageJS.ImageType.PNG}) 220 | .then(function() { 221 | // bitmap has been written and stream ended 222 | }); 223 | 224 | ``` 225 | 226 | # Release History 227 | 228 | | Version | Changes | 229 | | ------- | ------- | 230 | | 0.0.1 | Initial Version | 231 | | 0.0.2 | | 232 | | 0.0.3 | | 233 | | 0.0.5 | | 234 | | 0.0.6 | | 235 | | 0.0.8 | | 236 | | 0.0.9 | 237 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 Guyon Roche 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions:

10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | */ 23 | "use strict"; 24 | 25 | var _ = require("underscore"); 26 | 27 | var main = module.exports = { 28 | Bitmap: require("./lib/bitmap") 29 | }; 30 | 31 | _.extend(main, require("./lib/enums")); 32 | -------------------------------------------------------------------------------- /lib/bitmap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 Guyon Roche 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions:

10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | */ 23 | "use strict"; 24 | 25 | var fs = require("fs"); 26 | var _ = require("underscore"); 27 | var Promise = require("bluebird"); 28 | var jpeg = require("jpeg-js"); 29 | //var png = require("png-js"); 30 | var PNG = require("node-png").PNG; 31 | 32 | var Enums = require("./enums"); 33 | var Utils = require("./utils"); 34 | var Resize = require("./resize"); 35 | //var Graphics = require("./graphics"); 36 | 37 | // default pad colour 38 | var transparentBlack = { 39 | r: 0, g: 0, b: 0, a: 0 40 | }; 41 | 42 | var Bitmap = module.exports = function(options) { 43 | if (options) { 44 | if (options instanceof Bitmap) { 45 | this._data = { 46 | data: new Buffer(options.data.data), 47 | width: options.width, 48 | height: options.height 49 | }; 50 | } else if (options.data) { 51 | // attach to supplied data 52 | this._data = options; 53 | } else if (options.width && options.height) { 54 | // construct new bitmap 55 | this._data = { 56 | data: new Buffer(4 * options.width * options.height), 57 | width: options.width, 58 | height: options.height 59 | }; 60 | 61 | // optional colour 62 | if (options.color) { 63 | this._fill(options.color); 64 | } 65 | } 66 | } 67 | }; 68 | 69 | Bitmap.prototype = { 70 | get width() { 71 | return this._data.width; 72 | }, 73 | get height() { 74 | return this._data.height; 75 | }, 76 | //get graphics() { 77 | // if (!this._graphics) { 78 | // this._graphics = new Graphics(this); 79 | // } 80 | // return this._graphics; 81 | //}, 82 | 83 | attach: function(data) { 84 | var prev = this._data; 85 | this._data = data; 86 | return prev; 87 | }, 88 | detach: function() { 89 | var data = this._data; 90 | delete this._data; 91 | return data; 92 | }, 93 | 94 | _deduceFileType: function(filename) { 95 | if (!filename) { 96 | throw new Error("Can't determine image type"); 97 | } 98 | switch (filename.substr(-4).toLowerCase()) { 99 | case ".jpg": 100 | return Enums.ImageType.JPG; 101 | case ".png": 102 | return Enums.ImageType.PNG; 103 | } 104 | if (filename.substr(-5).toLowerCase() == ".jpeg") { 105 | return Enums.ImageType.JPG; 106 | } 107 | throw new Error("Can't recognise image type: " + filename); 108 | }, 109 | 110 | _readStream: function(stream) { 111 | var self = this; 112 | var deferred = Promise.defer(); 113 | 114 | var chunks = []; 115 | stream.on('data', function(chunk) { 116 | chunks.push(chunk); 117 | }); 118 | stream.on('end', function() { 119 | var data = Buffer.concat(chunks); 120 | deferred.resolve(data); 121 | }); 122 | stream.on('error', function(error) { 123 | deferred.reject(error); 124 | }); 125 | 126 | return deferred.promise; 127 | }, 128 | _readPNG: function(stream) { 129 | var deferred = Promise.defer(); 130 | 131 | var png = new PNG({filterType: 4}); 132 | png.on('parsed', function() { 133 | deferred.resolve(png); 134 | }); 135 | png.on('error', function(error) { 136 | deferred.reject(error); 137 | }); 138 | stream.pipe(png); 139 | 140 | return deferred.promise; 141 | }, 142 | _parseOptions: function(options, filename) { 143 | options = options || {}; 144 | if (typeof options === "number") { 145 | options = { type: options }; 146 | } 147 | options.type = options.type || this._deduceFileType(filename); 148 | return options; 149 | }, 150 | read: function(stream, options) { 151 | var self = this; 152 | options = this._parseOptions(options); 153 | 154 | switch(options.type) { 155 | case Enums.ImageType.JPG: 156 | return this._readStream(stream) 157 | .then(function(data) { 158 | self._data = jpeg.decode(data); 159 | }); 160 | case Enums.ImageType.PNG: 161 | return this._readPNG(stream) 162 | .then(function(png) { 163 | self._data = { 164 | data: png.data, 165 | width: png.width, 166 | height: png.height 167 | }; 168 | }); 169 | default: 170 | return Promise.reject(new Error("Not supported: ImageType " + options.type)); 171 | } 172 | }, 173 | readFile: function(filename, options) { 174 | var self = this; 175 | return Utils.fs.exists(filename) 176 | .then(function(exists) { 177 | if (exists) { 178 | options = self._parseOptions(options, filename); 179 | var stream = fs.createReadStream(filename); 180 | return self.read(stream, options); 181 | } else { 182 | throw new Error("File Not Found: " + filename); 183 | } 184 | }); 185 | }, 186 | 187 | write: function(stream, options) { 188 | options = this._parseOptions(options); 189 | var deferred = Promise.defer(); 190 | try { 191 | stream.on('finish', function() { 192 | deferred.resolve(); 193 | }); 194 | stream.on('error', function(error) { 195 | deferred.reject(error); 196 | }); 197 | 198 | switch(options.type) { 199 | case Enums.ImageType.JPG: 200 | var buffer = jpeg.encode(this._data, options.quality || 90).data; 201 | stream.write(buffer); 202 | stream.end(); 203 | break; 204 | case Enums.ImageType.PNG: 205 | var png = new PNG(); 206 | png.width = this.width; 207 | png.height = this.height; 208 | png.data = this._data.data; 209 | png.on('end', function() { 210 | deferred.resolve(); 211 | }); 212 | png.on('error', function(error) { 213 | deferred.reject(error); 214 | }); 215 | png.pack().pipe(stream); 216 | break; 217 | default: 218 | throw new Error("Not supported: ImageType " + options.type); 219 | } 220 | } 221 | catch(ex) { 222 | deferred.reject(ex); 223 | } 224 | return deferred.promise; 225 | }, 226 | writeFile: function(filename, options) { 227 | options = this._parseOptions(options, filename); 228 | var stream = fs.createWriteStream(filename); 229 | return this.write(stream, options); 230 | }, 231 | 232 | clone: function() { 233 | return new Bitmap({ 234 | width: this.width, 235 | height: this.height, 236 | data: new Buffer(this._data.data) 237 | }); 238 | }, 239 | 240 | setPixel: function(x,y, r,g,b,a) { 241 | if (g === undefined) { 242 | var color = r; 243 | r = color.r; 244 | g = color.g; 245 | b = color.b; 246 | a = color.a; 247 | } 248 | if (a === undefined) a = 255; 249 | var pos = (y * this.width + x) * 4; 250 | var buffer = this._data.data; 251 | buffer[pos++] = r; 252 | buffer[pos++] = g; 253 | buffer[pos++] = b; 254 | buffer[pos++] = a; 255 | }, 256 | getPixel: function(x,y, color) { 257 | var pos = (y * this.width + x) * 4; 258 | color = color || {}; 259 | var buffer = this._data.data; 260 | color.r = buffer[pos++]; 261 | color.g = buffer[pos++]; 262 | color.b = buffer[pos++]; 263 | color.a = buffer[pos++]; 264 | return color; 265 | }, 266 | 267 | negative: function() { 268 | var that = new Bitmap({width: this.width, height: this.height}); 269 | var n = this.width * this.height; 270 | 271 | var src = this._data.data; 272 | var dst = that._data.data; 273 | var srcPos = 0; 274 | var dstPos = 0; 275 | for (var i = 0; i < n; i++) { 276 | dst[dstPos++] = 255 - src[srcPos++]; 277 | dst[dstPos++] = 255 - src[srcPos++]; 278 | dst[dstPos++] = 255 - src[srcPos++]; 279 | dst[dstPos++] = src[srcPos++]; 280 | } 281 | return that; 282 | }, 283 | 284 | resize: function(options) { 285 | var that = new Bitmap(options); 286 | var temp; 287 | switch (options.fit) { 288 | case "pad": // fit all of src in dst with aspect ratio preserved. 289 | var padColor = options.padColor || transparentBlack; 290 | var srcAr = this.width / this.height; 291 | var w2 = Math.round(srcAr * that.height); 292 | var h2 = Math.round(that.width / srcAr); 293 | var wMargin = 0; 294 | var hMargin = 0; 295 | if (w2 < that.width) { 296 | // pad sides 297 | temp = new Bitmap({width: w2, height: that.height}); 298 | wMargin = (that.width - w2) / 2; 299 | that._fill(padColor, 0, 0, Math.floor(wMargin), that.height); 300 | that._fill(padColor, that.width - Math.ceil(wMargin), 0, Math.ceil(wMargin), that.height); 301 | 302 | Resize[options.algorithm](this, temp, options); 303 | that._blt(temp, {left: Math.floor(wMargin), top: Math.floor(hMargin)}); 304 | } else if (h2 < that.height) { 305 | // pad top & bottom 306 | temp = new Bitmap({width: that.width, height: h2}); 307 | hMargin = (that.height - h2) / 2; 308 | that._fill(padColor, 0, 0, that.width, Math.floor(hMargin)); 309 | that._fill(padColor, 0, that.height - Math.ceil(hMargin), that.width, Math.ceil(hMargin)); 310 | 311 | Resize[options.algorithm](this, temp, options); 312 | that._blt(temp, {left: Math.floor(wMargin), top: Math.floor(hMargin)}); 313 | } else { 314 | // stretch straight into that 315 | Resize[options.algorithm](this, that, options); 316 | } 317 | break; 318 | case "crop": // crop original to fit in dst with aspect ratio preserved 319 | var gravity = options.gravity || {x: 0.5, y: 0.5}; 320 | var dstAr = that.width / that.height; 321 | var w2 = Math.round(dstAr * this.height); 322 | var h2 = Math.round(this.width / dstAr); 323 | if (w2 < this.width) { 324 | // crop src width 325 | var dw = this.width - w2; 326 | temp = this.crop({left: Math.round(gravity.x * dw), top: 0, width: w2, height: this.height}); 327 | } else if (h2 < this.height) { 328 | // crop src height 329 | var dh = this.height - h2; 330 | temp = this.crop({left: 0, top: Math.round(gravity.y * dh), width: this.width, height: h2}); 331 | } else { 332 | temp = this; 333 | } 334 | Resize[options.algorithm](temp, that, options); 335 | break; 336 | case "stretch": 337 | default: 338 | Resize[options.algorithm](this, that, options); 339 | break; 340 | } 341 | 342 | return that; 343 | }, 344 | 345 | rotate: function(options) { 346 | // TODO: crop, user supplied dst width, height 347 | 348 | // options.degrees || options.radians; 349 | // options.fit = ['pad','crop','same'] 350 | // options.padColor 351 | var radians = options.radians !== undefined ? options.radians : 3.141592653589793 * options.degrees / 180; 352 | if (radians < 0.000000001) { 353 | return new Bitmap(this); 354 | } 355 | //console.log("radians=" + radians); 356 | 357 | var rotators = { 358 | forward: { 359 | cos: Math.cos(radians), 360 | sin: Math.sin(radians) 361 | }, 362 | backward: { 363 | cos: Math.cos(-radians), 364 | sin: Math.sin(-radians) 365 | } 366 | } 367 | //console.log("cos=" + cos + ", sin=" + sin) 368 | 369 | var srcWidth = this.width; 370 | var srcHeight = this.height; 371 | var srcWidthHalf = srcWidth / 2; 372 | var srcHeightHalf = srcHeight / 2; 373 | 374 | var padColor = options.padColor || transparentBlack; 375 | var padArray = [padColor.r, padColor.g, padColor.b, padColor.a]; 376 | var rotate = function(point, rotator) { 377 | // in-place rotation of point 378 | var x = rotator.cos * point.x - rotator.sin * point.y; 379 | var y = rotator.sin * point.x + rotator.cos * point.y; 380 | point.x = x; 381 | point.y = y; 382 | return point; 383 | }; 384 | var cropToSource = function(point) { 385 | var m = Math.abs(point.x/srcWidthHalf); 386 | var n = Math.abs(point.y/srcHeightHalf); 387 | return Math.max(m,n); 388 | }; 389 | 390 | var dstWidth, dstHeight; 391 | switch (options.fit) { 392 | case 'custom': 393 | dstWidth = options.width; 394 | dstHeight = options.height; 395 | break; 396 | case 'pad': 397 | // entire src fits in dst 398 | var tl = rotate({x:-srcWidthHalf,y:srcHeightHalf}, rotators.forward); 399 | var tr = rotate({x:srcWidthHalf,y:srcHeightHalf}, rotators.forward); 400 | var bl = rotate({x:-srcWidthHalf,y:-srcHeightHalf}, rotators.forward); 401 | var br = rotate({x:srcWidthHalf,y:-srcHeightHalf}, rotators.forward); 402 | dstWidth = Math.round(Math.max(tl.x,tr.x,bl.x,br.x) - Math.min(tl.x,tr.x,bl.x,br.x)); 403 | dstHeight = Math.round(Math.max(tl.y,tr.y,bl.y,br.y) - Math.min(tl.y,tr.y,bl.y,br.y)); 404 | break; 405 | case 'crop': 406 | var tl = rotate({x:-srcWidthHalf,y:srcHeightHalf}, rotators.forward); 407 | var tr = rotate({x:srcWidthHalf,y:srcHeightHalf}, rotators.forward); 408 | var bl = rotate({x:-srcWidthHalf,y:-srcHeightHalf}, rotators.forward); 409 | var br = rotate({x:srcWidthHalf,y:-srcHeightHalf}, rotators.forward); 410 | var d = Math.max(cropToSource(tl), cropToSource(tr), cropToSource(bl), cropToSource(br)); 411 | dstWidth = Math.floor(srcWidth / d); 412 | dstHeight = Math.floor(srcHeight / d); 413 | break; 414 | case 'same': 415 | default: 416 | // dst is same size as src 417 | dstWidth = srcWidth; 418 | dstHeight = srcHeight; 419 | break; 420 | } 421 | 422 | var that = new Bitmap({width: dstWidth, height: dstHeight}); 423 | 424 | var srcBuf = this._data.data; 425 | var dstBuf = that._data.data; 426 | 427 | // we will rotate the destination pixels back to the source and interpolate the colour 428 | var srcCoord = {}; 429 | var dstWidthHalf = dstWidth / 2; 430 | var dstHeightHalf = dstHeight / 2; 431 | var dstWidth4 = dstWidth * 4; 432 | var srcWidth4 = srcWidth * 4; 433 | 434 | //console.log("src=[" + srcWidth + "," + srcHeight + "]") 435 | //console.log("dst=[" + dstWidth + "," + dstHeight + "]") 436 | for (var i = 0; i < dstHeight; i++) { 437 | for (var j = 0; j < dstWidth; j++) { 438 | // calculate src coords 439 | srcCoord.x = j - dstWidthHalf; 440 | srcCoord.y = dstHeightHalf - i; 441 | //console.log("x=" + srcCoord.x + ", y=" + srcCoord.y); 442 | rotate(srcCoord, rotators.backward); 443 | //console.log(" ==> x=" + srcCoord.x + ", y=" + srcCoord.y); 444 | 445 | // srcX and SrcY are in src coords 446 | var srcX = srcCoord.x + srcWidthHalf; 447 | var srcY = srcHeightHalf - srcCoord.y; 448 | //console.log("srcX=" + srcX + ", srcY=" + srcY); 449 | 450 | // now interpolate (bilinear! 451 | var dstPos = (i * dstWidth + j) * 4; 452 | //console.log("dstPos=" + dstPos) 453 | if ((srcX > -1) && (srcX < srcWidth) && (srcY > -1) && (srcY < srcHeight)) { 454 | var srcPosX = Math.floor(srcX); 455 | var srcPosY = Math.floor(srcY); 456 | var srcPos = (srcPosY * srcWidth + srcPosX) * 4; 457 | for (var k = 0; k < 4; k++) { 458 | var kSrcPos = srcPos + k; 459 | var kPad = padArray[k]; 460 | 461 | var tl = ((srcX >= 0) && (srcY >= 0)) ? srcBuf[kSrcPos] : kPad; 462 | var tr = ((srcX < srcWidth-1) && (srcY >= 0)) ? srcBuf[kSrcPos+4] : kPad; 463 | var bl = ((srcX >= 0) && (srcY < srcHeight-1)) ? srcBuf[kSrcPos + srcWidth4] : kPad; 464 | var br = ((srcX < srcWidth-1) && (srcY < srcHeight-1)) ? srcBuf[kSrcPos + srcWidth4 + 4] : kPad; 465 | 466 | var tx = srcX - srcPosX; 467 | var ty = srcY - srcPosY; 468 | 469 | var t = (1-tx) * tl + tx * tr; 470 | var b = (1-tx) * bl + tx * br; 471 | dstBuf[dstPos++] = (1-ty) * t + ty * b; 472 | } 473 | } else { 474 | dstBuf[dstPos++] = padColor.r; 475 | dstBuf[dstPos++] = padColor.g; 476 | dstBuf[dstPos++] = padColor.b; 477 | dstBuf[dstPos++] = padColor.a; 478 | } 479 | } 480 | } 481 | return that; 482 | }, 483 | 484 | crop: function(options) { 485 | var t = options.top; 486 | var l = options.left; 487 | var w = options.width; 488 | var h = options.height; 489 | //console.log("Crop: l="+l + ", t="+t + ", w="+w + ", h="+h); 490 | 491 | var that = new Bitmap({width: w, height: h}); 492 | 493 | var srcBuf = this._data.data; 494 | var dstBuf = that._data.data; 495 | 496 | var w4 = w * 4; 497 | for (var i = 0; i < h; i++) { 498 | var srcPos = ((i+t)*this.width + l) * 4; 499 | var dstPos = i * w * 4; 500 | srcBuf.copy(dstBuf, dstPos, srcPos, srcPos + w4); 501 | } 502 | return that; 503 | }, 504 | 505 | blur: function(options) { 506 | // todo: expand to own file with different blur algorithms 507 | var that = new Bitmap({width: this.width, height: this.height}); 508 | var w = this.width; 509 | var h = this.height; 510 | 511 | var W = w-1; 512 | var H = h-1; 513 | 514 | var V = w*4; // used for i offsets 515 | 516 | var src = this._data.data; 517 | var dst = that._data.data; 518 | for (var i = 0; i < h; i++) { 519 | for (var j = 0; j < w; j++) { 520 | for (var k = 0; k < 4; k++) { 521 | var pos = (i*w + j) * 4 + k; 522 | var t = src[pos -(i>0?V:0) - (j>0?4:0)] * 1 + // 1/16 523 | src[pos -(i>0?V:0) ] * 2 + // 2/16 524 | src[pos -(i>0?V:0) + (j0?4:0)] * 2 + // 2/16 527 | src[pos ] * 4 + // 4/16 528 | src[pos + (j0?4:0)] * 1 + // 1/16 531 | src[pos +(i 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | */ 23 | "use strict"; 24 | 25 | var _ = require("underscore"); 26 | var Promise = require("bluebird"); 27 | 28 | var Enums = require("./enums"); 29 | var Utils = require("./utils"); 30 | 31 | // default pad colour 32 | var transparentBlack = { 33 | r: 0, g: 0, b: 0, a: 0 34 | }; 35 | 36 | var SolidBrush = function(options) { 37 | this.color = options.color; 38 | }; 39 | 40 | SolidBrush.prototype = { 41 | getColor: function(x,y, left, top, color) { 42 | color = color || {}; 43 | color.r = this.color.r; 44 | color.g = this.color.g; 45 | color.b = this.color.b; 46 | color.a = this.color.a; 47 | return color; 48 | } 49 | }; 50 | 51 | var GradientBrush = function(options) { 52 | switch (options.gradient) { 53 | case "direction": 54 | this.type = 1; 55 | var angle = options.radians !== undefined ? options.radians : 3.141592653589793 * options.degrees / 180; 56 | this.sin = Math.sin(angle); 57 | this.cos = Math.cos(angle); 58 | this. 59 | break; 60 | case "radial": 61 | this.type = 2; 62 | this.center = options.center; 63 | this.radius = this._calculateRadius(this.center.x,this.center.y) 64 | break; 65 | } 66 | this.path = options.path; 67 | }; 68 | 69 | GradientBrush.prototype = { 70 | 71 | getColor: function(x,y, left, top, color) { 72 | color = color || {}; 73 | switch (this.type) { 74 | case 1: 75 | var x2 = x - 0.5; 76 | var y2 = y - 0.5; 77 | var x3 = this.cos * x2 - this.sin * y2; 78 | //var y3 = this.sin * x2 + this.cos * y2; 79 | return this._interpolateColor(x3, color); 80 | case 2: 81 | var dx = x - this.center.x; 82 | var dy = y - this.center.y; 83 | var d = Math.sqrt(dx*dx+dy*dy); 84 | return this._interpolateColor(d, color); 85 | default: 86 | return transparentBlack; 87 | } 88 | }, 89 | 90 | _calculateRadius: function(x,y) { 91 | var maxX = Math.max(x, 1-x); 92 | var maxY = Math.max(y, 1-y); 93 | return Math.sqrt(maxX*maxX + maxY*maxY); 94 | }, 95 | 96 | _interpolateColor: function(d, color) { 97 | // expecting this.path to have steps from 0 to 1 98 | var index = 0; 99 | while ((index < this.path.length-1) && (d >= this.path[index+1].position)) { 100 | index++; 101 | } 102 | var step1 = this.path[index]; 103 | var step2 = this.path[index+1]; 104 | var coeff1 = step2.position - d; 105 | var coeff2 = d - step1.position; 106 | var divisor = step2.position - step1.position; 107 | color.r = (coeff1 * step1.color.r + coeff2 * step2.color.r) / divisor; 108 | color.g = (coeff1 * step1.color.g + coeff2 * step2.color.g) / divisor; 109 | color.b = (coeff1 * step1.color.b + coeff2 * step2.color.b) / divisor; 110 | color.a = (coeff1 * step1.color.a + coeff2 * step2.color.a) / divisor; 111 | return color; 112 | } 113 | }; 114 | 115 | module.exports = { 116 | Solid: SolidBrush, 117 | Gradient: GradientBrush 118 | } -------------------------------------------------------------------------------- /lib/enums.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 Guyon Roche 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions:

10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | */ 23 | "use strict"; 24 | 25 | module.exports = { 26 | ImageType: { 27 | JPG: 1, 28 | PNG: 2 29 | } 30 | }; -------------------------------------------------------------------------------- /lib/graphics.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 Guyon Roche 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions:

10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | */ 23 | "use strict"; 24 | 25 | var _ = require("underscore"); 26 | var Promise = require("bluebird"); 27 | 28 | var Enums = require("./enums"); 29 | var Utils = require("./utils"); 30 | 31 | // default pad colour 32 | var transparentBlack = { 33 | r: 0, g: 0, b: 0, a: 0 34 | }; 35 | 36 | var Graphics = module.exports = function(bitmap) { 37 | this.bitmap = bitmap; 38 | }; 39 | 40 | Graphics.prototype = { 41 | get width() { 42 | return this.bitmap.width; 43 | }, 44 | get height() { 45 | return this.bitmap.height; 46 | }, 47 | get buffer() { 48 | return this.bitmap._data.data; 49 | }, 50 | 51 | setPixel: function(x,y, r,g,b,a) { 52 | if (g === undefined) { 53 | var color = r; 54 | r = color.r; 55 | g = color.g; 56 | b = color.b; 57 | a = color.a; 58 | } 59 | if (a === undefined) a = 255; 60 | var pos = (y * this.width + x) * 4; 61 | var buffer = this.buffer; 62 | buffer[pos++] = r; 63 | buffer[pos++] = g; 64 | buffer[pos++] = b; 65 | buffer[pos++] = a; 66 | }, 67 | getPixel: function(x,y, color) { 68 | var pos = (y * this.width + x) * 4; 69 | color = color || {}; 70 | var buffer = this.buffer; 71 | color.r = buffer[pos++]; 72 | color.g = buffer[pos++]; 73 | color.b = buffer[pos++]; 74 | color.a = buffer[pos++]; 75 | return color; 76 | }, 77 | 78 | fillRect: function(left, top, width, height, options) { 79 | // to do this properly requires a brush class that can return a color for a given x,y 80 | // position (i.e. texture painting). Can the texture be transformed? 81 | // Can the rect be transformed? 82 | 83 | var fillColor = options.fillColor; 84 | this.bitmap._fill(fillColor, left, top, width, height); 85 | }, 86 | 87 | drawLine: function(x1,y1,x2,y2, options) { 88 | 89 | }, 90 | 91 | drawImage: function(image, options) { 92 | 93 | // left,top,width,height refer to draw window in dst 94 | var dstLeft = options.left || 0; 95 | var dstTop = options.top || 0; 96 | var dstWidth = options.width || image.width; 97 | var dstHeight = options.height || image.height; 98 | if ((dstWidth != image.width) || (dstHeight != image.height)) { 99 | image = image.resize({width: dstWidth, height: dstHeight, algorithm: "bezierInterpolation"}); 100 | } 101 | 102 | // this.bitmap's dimensions 103 | var bmpWidth = this.width; 104 | var bmpHeight = this.height; 105 | var bmpW4 = bmpWidth * 4; 106 | 107 | // source image crop window 108 | var srcLeft = 0; 109 | var srcTop = 0; 110 | var srcWidth = image.width; 111 | var srcHeight = image.height; 112 | 113 | // crop dstLeft,dstTop,dstWidth,dstHeight to this.bitmap 114 | if (dstLeft < 0) { 115 | dstWidth = dstWidth + dstLeft; 116 | srcLeft = srcLeft - dstLeft; 117 | dstLeft = 0; 118 | } 119 | if (dstTop < 0) { 120 | dstHeight = dstHeight + dstTop; 121 | srcTop = srcTop - dstTop; 122 | dstTop = 0; 123 | } 124 | if (dstLeft > bmpWidth) return; 125 | if (dstTop > bmpHeight) return; 126 | if (dstWidth <= 0) return; 127 | if (dstHeight <= 0) return; 128 | 129 | //console.log("_blt: left="+left + ", top="+top + ", width="+width + ", height="+height) 130 | var srcBuf = image._data.data; 131 | var dstBuf = this.buffer; 132 | 133 | var dstL4 = dstLeft * 4; 134 | var srcL4 = srcLeft * 4; 135 | var srcW4 = srcWidth * 4; 136 | 137 | for (var i = 0; i < dstHeight; i++) { 138 | var srcPos = (i + srcTop) * srcW4 + dstL4; 139 | var dstPos = (i + dstTop) * bmpW4 + dstL4; 140 | 141 | for (var j = 0; j < dstWidth; j++) { 142 | var srcR = srcBuf[srcPos++]; 143 | var srcG = srcBuf[srcPos++]; 144 | var srcB = srcBuf[srcPos++]; 145 | var srcA = srcBuf[srcPos++]; 146 | 147 | if (srcA === 255) { 148 | dstBuf[dstPos++] = srcR; 149 | dstBuf[dstPos++] = srcG; 150 | dstBuf[dstPos++] = srcB; 151 | 152 | // destination transparency not affected 153 | dstPos++; 154 | } else if (srcA === 0) { 155 | dstPos += 4; 156 | } else { 157 | var dstR = dstBuf[dstPos]; 158 | dstBuf[dstPos++] = Math.round((srcR * srcA + dstR * (255-srcA)) / 255); 159 | 160 | var dstG = dstBuf[dstPos]; 161 | dstBuf[dstPos++] = Math.round((srcG * srcA + dstG * (255-srcA)) / 255); 162 | 163 | var dstB = dstBuf[dstPos]; 164 | dstBuf[dstPos++] = Math.round((srcB * srcA + dstB * (255-srcA)) / 255); 165 | 166 | dstPos++; 167 | } 168 | } 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /lib/resize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 Guyon Roche 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions:

10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | */ 23 | "use strict"; 24 | 25 | var _ = require("underscore"); 26 | var Promise = require("bluebird"); 27 | 28 | module.exports = { 29 | _writeFile: function(width, height, data, filename) { 30 | // for debugging 31 | 32 | var Bitmap = require("./bitmap"); 33 | var bmp = new Bitmap({ 34 | width: width, height: height, 35 | data: data 36 | }); 37 | bmp.writeFile(filename); 38 | }, 39 | 40 | nearestNeighbor: function(src, dst, options) { 41 | 42 | var wSrc = src.width; 43 | var hSrc = src.height; 44 | //console.log("wSrc="+wSrc + ", hSrc="+hSrc); 45 | 46 | var wDst = dst.width; 47 | var hDst = dst.height; 48 | //console.log("wDst="+wDst + ", hDst="+hDst); 49 | 50 | var bufSrc = src._data.data; 51 | var bufDst = dst._data.data; 52 | 53 | for (var i = 0; i < hDst; i++) { 54 | for (var j = 0; j < wDst; j++) { 55 | var posDst = (i * wDst + j) * 4; 56 | 57 | var iSrc = Math.round(i * hSrc / hDst); 58 | var jSrc = Math.round(j * wSrc / wDst); 59 | var posSrc = (iSrc * wSrc + jSrc) * 4; 60 | 61 | bufDst[posDst++] = bufSrc[posSrc++]; 62 | bufDst[posDst++] = bufSrc[posSrc++]; 63 | bufDst[posDst++] = bufSrc[posSrc++]; 64 | bufDst[posDst++] = bufSrc[posSrc++]; 65 | } 66 | } 67 | }, 68 | bilinearInterpolation: function(src, dst, options) { 69 | 70 | var wSrc = src.width; 71 | var hSrc = src.height; 72 | //console.log("wSrc="+wSrc + ", hSrc="+hSrc); 73 | 74 | var wDst = dst.width; 75 | var hDst = dst.height; 76 | //console.log("wDst="+wDst + ", hDst="+hDst); 77 | 78 | var bufSrc = src._data.data; 79 | var bufDst = dst._data.data; 80 | 81 | var interpolate = function(k, kMin, vMin, kMax, vMax) { 82 | // special case - k is integer 83 | if (kMin === kMax) { 84 | return vMin; 85 | } 86 | 87 | return Math.round((k - kMin) * vMax + (kMax - k) * vMin); 88 | }; 89 | var assign = function(pos, offset, x, xMin, xMax, y, yMin, yMax) { 90 | var posMin = (yMin * wSrc + xMin) * 4 + offset; 91 | var posMax = (yMin * wSrc + xMax) * 4 + offset; 92 | var vMin = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]); 93 | 94 | // special case, y is integer 95 | if (yMax === yMin) { 96 | bufDst[pos+offset] = vMin; 97 | } else { 98 | posMin = (yMax * wSrc + xMin) * 4 + offset; 99 | posMax = (yMax * wSrc + xMax) * 4 + offset; 100 | var vMax = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]); 101 | 102 | bufDst[pos+offset] = interpolate(y, yMin, vMin, yMax, vMax); 103 | } 104 | } 105 | 106 | for (var i = 0; i < hDst; i++) { 107 | for (var j = 0; j < wDst; j++) { 108 | var posDst = (i * wDst + j) * 4; 109 | 110 | // x & y in src coordinates 111 | var x = j * wSrc / wDst; 112 | var xMin = Math.floor(x); 113 | var xMax = Math.min(Math.ceil(x), wSrc-1); 114 | 115 | var y = i * hSrc / hDst; 116 | var yMin = Math.floor(y); 117 | var yMax = Math.min(Math.ceil(y), hSrc-1); 118 | 119 | assign(posDst, 0, x, xMin, xMax, y, yMin, yMax); 120 | assign(posDst, 1, x, xMin, xMax, y, yMin, yMax); 121 | assign(posDst, 2, x, xMin, xMax, y, yMin, yMax); 122 | assign(posDst, 3, x, xMin, xMax, y, yMin, yMax); 123 | } 124 | } 125 | }, 126 | 127 | _interpolate2D: function(src, dst, options, interpolate) { 128 | 129 | var bufSrc = src._data.data; 130 | var bufDst = dst._data.data; 131 | 132 | var wSrc = src.width; 133 | var hSrc = src.height; 134 | //console.log("wSrc="+wSrc + ", hSrc="+hSrc + ", srcLen="+bufSrc.length); 135 | 136 | var wDst = dst.width; 137 | var hDst = dst.height; 138 | //console.log("wDst="+wDst + ", hDst="+hDst + ", dstLen="+bufDst.length); 139 | 140 | // when dst smaller than src/2, interpolate first to a multiple between 0.5 and 1.0 src, then sum squares 141 | var wM = Math.max(1, Math.floor(wSrc / wDst)); 142 | var wDst2 = wDst * wM; 143 | var hM = Math.max(1, Math.floor(hSrc / hDst)); 144 | var hDst2 = hDst * hM; 145 | //console.log("wM="+wM + ", wDst2="+wDst2 + ", hM="+hM + ", hDst2="+hDst2); 146 | 147 | // =========================================================== 148 | // Pass 1 - interpolate rows 149 | // buf1 has width of dst2 and height of src 150 | var buf1 = new Buffer(wDst2 * hSrc * 4); 151 | for (var i = 0; i < hSrc; i++) { 152 | for (var j = 0; j < wDst2; j++) { 153 | // i in src coords, j in dst coords 154 | 155 | // calculate x in src coords 156 | // this interpolation requires 4 sample points and the two inner ones must be real 157 | // the outer points can be fudged for the edges. 158 | // therefore (wSrc-1)/wDst2 159 | var x = j * (wSrc-1) / wDst2; 160 | var xPos = Math.floor(x); 161 | var t = x - xPos; 162 | var srcPos = (i * wSrc + xPos) * 4; 163 | 164 | var buf1Pos = (i * wDst2 + j) * 4; 165 | for (var k = 0; k < 4; k++) { 166 | var kPos = srcPos + k; 167 | var x0 = (xPos > 0) ? bufSrc[kPos - 4] : 2*bufSrc[kPos]-bufSrc[kPos+4]; 168 | var x1 = bufSrc[kPos]; 169 | var x2 = bufSrc[kPos + 4]; 170 | var x3 = (xPos < wSrc - 2) ? bufSrc[kPos + 8] : 2*bufSrc[kPos + 4]-bufSrc[kPos]; 171 | buf1[buf1Pos+k] = interpolate(x0,x1,x2,x3,t); 172 | } 173 | } 174 | } 175 | //this._writeFile(wDst2, hSrc, buf1, "out/buf1.jpg"); 176 | 177 | // =========================================================== 178 | // Pass 2 - interpolate columns 179 | // buf2 has width and height of dst2 180 | var buf2 = new Buffer(wDst2 * hDst2 * 4); 181 | for (var i = 0; i < hDst2; i++) { 182 | for (var j = 0; j < wDst2; j++) { 183 | // i&j in dst2 coords 184 | 185 | // calculate y in buf1 coords 186 | // this interpolation requires 4 sample points and the two inner ones must be real 187 | // the outer points can be fudged for the edges. 188 | // therefore (hSrc-1)/hDst2 189 | var y = i * (hSrc-1) / hDst2; 190 | var yPos = Math.floor(y); 191 | var t = y - yPos; 192 | var buf1Pos = (yPos * wDst2 + j) * 4; 193 | var buf2Pos = (i * wDst2 + j) * 4; 194 | for (var k = 0; k < 4; k++) { 195 | var kPos = buf1Pos + k; 196 | var y0 = (yPos > 0) ? buf1[kPos - wDst2*4] : 2*buf1[kPos]-buf1[kPos + wDst2*4]; 197 | var y1 = buf1[kPos]; 198 | var y2 = buf1[kPos + wDst2*4]; 199 | var y3 = (yPos < hSrc-2) ? buf1[kPos + wDst2*8] : 2*buf1[kPos + wDst2*4]-buf1[kPos]; 200 | 201 | buf2[buf2Pos + k] = interpolate(y0,y1,y2,y3,t); 202 | } 203 | } 204 | } 205 | //this._writeFile(wDst2, hDst2, buf2, "out/buf2.jpg"); 206 | 207 | // =========================================================== 208 | // Pass 3 - scale to dst 209 | var m = wM * hM; 210 | if (m > 1) { 211 | for (var i = 0; i < hDst; i++) { 212 | for (var j = 0; j < wDst; j++) { 213 | // i&j in dst bounded coords 214 | var r = 0; 215 | var g = 0; 216 | var b = 0; 217 | var a = 0; 218 | for (var y = 0; y < hM; y++) { 219 | var yPos = i * hM + y; 220 | for (var x = 0; x < wM; x++) { 221 | var xPos = j * wM + x; 222 | var xyPos = (yPos * wDst2 + xPos) * 4; 223 | r += buf2[xyPos]; 224 | g += buf2[xyPos+1]; 225 | b += buf2[xyPos+2]; 226 | a += buf2[xyPos+3]; 227 | } 228 | } 229 | 230 | var pos = (i*wDst + j) * 4; 231 | bufDst[pos] = Math.round(r / m); 232 | bufDst[pos+1] = Math.round(g / m); 233 | bufDst[pos+2] = Math.round(b / m); 234 | bufDst[pos+3] = Math.round(a / m); 235 | } 236 | } 237 | } else { 238 | // replace dst buffer with buf2 239 | dst._data.data = buf2; 240 | } 241 | }, 242 | 243 | bicubicInterpolation: function(src, dst, options) { 244 | var interpolateCubic = function(x0, x1, x2, x3, t) { 245 | var a0 = x3 - x2 - x0 + x1; 246 | var a1 = x0 - x1 - a0; 247 | var a2 = x2 - x0; 248 | var a3 = x1; 249 | return Math.max(0,Math.min(255,(a0 * (t * t * t)) + (a1 * (t * t)) + (a2 * t) + (a3))); 250 | } 251 | return this._interpolate2D(src, dst, options, interpolateCubic); 252 | }, 253 | 254 | hermiteInterpolation: function(src, dst, options) { 255 | var interpolateHermite = function(x0, x1, x2, x3, t) 256 | { 257 | var c0 = x1; 258 | var c1 = 0.5 * (x2 - x0); 259 | var c2 = x0 - (2.5 * x1) + (2 * x2) - (0.5 * x3); 260 | var c3 = (0.5 * (x3 - x0)) + (1.5 * (x1 - x2)); 261 | return Math.max(0,Math.min(255,Math.round((((((c3 * t) + c2) * t) + c1) * t) + c0))); 262 | } 263 | return this._interpolate2D(src, dst, options, interpolateHermite); 264 | }, 265 | 266 | bezierInterpolation: function(src, dst, options) { 267 | // between 2 points y(n), y(n+1), use next points out, y(n-1), y(n+2) 268 | // to predict control points (a & b) to be placed at n+0.5 269 | // ya(n) = y(n) + (y(n+1)-y(n-1))/4 270 | // yb(n) = y(n+1) - (y(n+2)-y(n))/4 271 | // then use std bezier to interpolate [n,n+1) 272 | // y(n+t) = y(n)*(1-t)^3 + 3 * ya(n)*(1-t)^2*t + 3 * yb(n)*(1-t)*t^2 + y(n+1)*t^3 273 | // note the 3* factor for the two control points 274 | // for edge cases, can choose: 275 | // y(-1) = y(0) - 2*(y(1)-y(0)) 276 | // y(w) = y(w-1) + 2*(y(w-1)-y(w-2)) 277 | // but can go with y(-1) = y(0) and y(w) = y(w-1) 278 | var interpolateBezier = function(x0, x1, x2, x3, t) { 279 | // x1, x2 are the knots, use x0 and x3 to calculate control points 280 | var cp1 = x1 + (x2-x0)/4; 281 | var cp2 = x2 - (x3-x1)/4; 282 | var nt = 1-t; 283 | var c0 = x1 * nt * nt * nt; 284 | var c1 = 3 * cp1 * nt * nt * t; 285 | var c2 = 3 * cp2 * nt * t * t; 286 | var c3 = x2 * t * t * t; 287 | return Math.max(0,Math.min(255,Math.round(c0 + c1 + c2 + c3))); 288 | } 289 | return this._interpolate2D(src, dst, options, interpolateBezier); 290 | } 291 | } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015 Guyon Roche 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions:

10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | */ 23 | "use strict"; 24 | 25 | var fs = require("fs"); 26 | var _ = require("underscore"); 27 | var Promise = require("bluebird"); 28 | 29 | 30 | var utils = module.exports = { 31 | 32 | // Promisification of fs 33 | fs: { 34 | exists: function(filename) { 35 | var deferred = Promise.defer(); 36 | fs.exists(filename, function(exists) { 37 | deferred.resolve(exists); 38 | }); 39 | return deferred.promise; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imagejs", 3 | "version": "0.0.9", 4 | "description": "Image Processor", 5 | "private": false, 6 | "license": "MIT", 7 | "author": { 8 | "name": "Guyon Roche", 9 | "email": "cyber.sapiens@hotmail.com", 10 | "url": "https://github.com/guyonroche/imagejs" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/guyonroche/imagejs.git" 15 | }, 16 | "keywords": [ 17 | "pure", "javascript", "image", "bitmap", "jpg", "jpeg", "resize", "rotate", "negative", "crop", 18 | "bilinear", "bicubic", "hermite", "bezier" 19 | ], 20 | "dependencies": { 21 | "bluebird": "*", 22 | "jpeg-js": "0.1.1", 23 | "node-png": "0.4.3", 24 | "underscore": "1.4.4" 25 | }, 26 | "devDependencies": { 27 | "jasmine-node": "~1.14", 28 | "memorystream": "*" 29 | }, 30 | "scripts": { 31 | "test": "jasmine-node spec" 32 | }, 33 | "main": "./index.js", 34 | "files": [ 35 | "index.js", 36 | "lib", 37 | "LICENSE", 38 | "README.md" 39 | ] 40 | } -------------------------------------------------------------------------------- /spec/blur.spec.js: -------------------------------------------------------------------------------- 1 | var _ = require("underscore"); 2 | 3 | var ImageJS = require("../index"); 4 | var Bitmap = ImageJS.Bitmap; 5 | 6 | describe("Bitmap.blur", function() { 7 | var solidRed = {r: 255, g: 0, b: 0, a: 255}; 8 | var solidBlue = {r: 0, g: 0, b: 255, a: 255}; 9 | var solidWhite = {r: 255, g: 255, b: 255, a: 255}; 10 | it("blurs lines", function() { 11 | var img1 = new Bitmap({width: 40, height: 30, color: solidWhite}); 12 | for (var i = 0; i < 30; i++) { 13 | img1.setPixel(15,i, solidRed); 14 | } 15 | for (var i = 0; i < 40; i++) { 16 | img1.setPixel(i,15, solidRed); 17 | } 18 | var img2 = img1.blur(); 19 | var color = {}; 20 | 21 | //corners should still be white 22 | expect(img2.getPixel( 1, 1,color)).toEqual(solidWhite); 23 | expect(img2.getPixel(38, 1,color)).toEqual(solidWhite); 24 | expect(img2.getPixel( 1,28,color)).toEqual(solidWhite); 25 | expect(img2.getPixel(38,28,color)).toEqual(solidWhite); 26 | 27 | var checkPoints = function(points, min, max) { 28 | _.each(points, function(coords){ 29 | color = img2.getPixel(coords.x,coords.y,color); 30 | //console.log(coords.x + "," + coords.y + " = " + JSON.stringify(color)); 31 | expect(color.r).toEqual(255); 32 | expect(color.g).toBeLessThan(max); 33 | expect(color.g).toBeGreaterThan(min); 34 | expect(color.b).toBeLessThan(max); 35 | expect(color.b).toBeGreaterThan(min); 36 | expect(color.a).toEqual(255); 37 | }); 38 | } 39 | 40 | // lines should be whitish red 41 | var lines = [ 42 | {x: 0,y:15}, {x: 1,y:15}, {x: 8,y:15}, {x:20,y:15}, {x:38,y:15}, {x:39,y:15}, 43 | {x:15,y: 0}, {x:15,y: 1}, {x:15,y: 8}, {x:15,y:20}, {x:15,y:28}, {x:15,y:29} 44 | ]; 45 | checkPoints(lines, 120, 136); 46 | 47 | // near the cross should be redder 48 | var cross = [ 49 | {x:15,y:14}, 50 | {x:14,y:15}, {x:15,y:15}, {x:16,y:15}, 51 | {x:15,y:16} 52 | ]; 53 | checkPoints(cross, 50, 100); 54 | 55 | // beside lines should be reddish white 56 | var besides = [ 57 | {x: 0,y:14}, {x: 1,y:14}, {x: 8,y:14}, {x:20,y:14}, {x:28,y:14}, {x:29,y:14}, 58 | {x: 0,y:16}, {x: 1,y:16}, {x: 8,y:16}, {x:20,y:16}, {x:28,y:16}, {x:29,y:16}, 59 | {x:14,y: 0}, {x:14,y: 1}, {x:14,y: 8}, {x:14,y:20}, {x:14,y:27}, {x:14,y:28}, 60 | {x:16,y: 0}, {x:16,y: 1}, {x:16,y: 8}, {x:16,y:20}, {x:16,y:27}, {x:16,y:28} 61 | ]; 62 | checkPoints(besides, 185, 195); 63 | 64 | var innerCorner = [ 65 | {x:14,y:14}, {x:16,y:14}, 66 | {x:14,y:16}, {x:16,y:16} 67 | ]; 68 | checkPoints(innerCorner, 130, 150); 69 | }); 70 | }); -------------------------------------------------------------------------------- /spec/resize.spec.js: -------------------------------------------------------------------------------- 1 | var _ = require("underscore"); 2 | 3 | var ImageJS = require("../index"); 4 | var Bitmap = ImageJS.Bitmap; 5 | 6 | describe("Bitmap.resize", function() { 7 | var solidRed = {r: 255, g: 0, b: 0, a: 255}; 8 | var solidBlue = {r: 0, g: 0, b: 255, a: 255}; 9 | var solidWhite = {r: 255, g: 255, b: 255, a: 255}; 10 | it("pads top and bottom", function() { 11 | var img1 = new Bitmap({width: 100, height: 50, color: solidBlue }); 12 | var img2 = img1.resize({width: 200, height: 200, algorithm: "bezierInterpolation", fit: "pad", padColor: solidRed}); 13 | 14 | // expect top and bottom strips of red 15 | var color = {}; 16 | var reds = [ 17 | {x:0,y:0}, {x:100,y:0}, {x:199,y:0}, {x:0,y:49}, {x:100,y:49}, {x:199,y:49}, 18 | {x:0,y:150}, {x:100,y:150}, {x:199,y:150}, {x:0,y:199}, {x:100,y:199}, {x:199,y:199} 19 | ]; 20 | _.each(reds, function(coords){ 21 | expect(img2.getPixel(coords.x, coords.y, color)).toEqual(solidRed); 22 | }); 23 | 24 | // and a middle of blue 25 | var blues = [ 26 | {x:0,y:50}, {x:100,y:50}, {x:199,y:50}, {x:0,y:100}, {x:100,y:100}, {x:199,y:100}, 27 | {x:0,y:149}, {x:100,y:149}, {x:199,y:149} 28 | ]; 29 | _.each(blues, function(coords){ 30 | expect(img2.getPixel(coords.x, coords.y, color)).toEqual(solidBlue); 31 | }); 32 | }); 33 | it("pads left and right", function() { 34 | var img1 = new Bitmap({width: 30, height: 50, color: solidBlue }); 35 | var img2 = img1.resize({width: 200, height: 200, algorithm: "bezierInterpolation", fit: "pad", padColor: solidRed}); 36 | 37 | // expect top and bottom strips of red 38 | var color = {}; 39 | var reds = [ 40 | {x:0,y:0}, {x:0,y:100}, {x:0,y:199}, {x:25,y:0}, {x:25,y:100}, {x:25,y:199}, 41 | {x:175,y:0}, {x:175,y:100}, {x:175,y:199}, {x:199,y:0}, {x:199,y:100}, {x:199,y:199} 42 | ]; 43 | _.each(reds, function(coords){ 44 | expect(img2.getPixel(coords.x, coords.y, color)).toEqual(solidRed); 45 | }); 46 | 47 | // and a middle of blue 48 | var blues = [ 49 | {x:50,y:0}, {x:50,y:100}, {x:50,y:199}, 50 | {x:100,y:0}, {x:100,y:100}, {x:100,y:199}, 51 | {x:150,y:0}, {x:150,y:100}, {x:150,y:199} 52 | ]; 53 | _.each(blues, function(coords){ 54 | expect(img2.getPixel(coords.x, coords.y, color)).toEqual(solidBlue); 55 | }); 56 | }); 57 | it("interpolates finer detail", function() { 58 | var img1 = new Bitmap({width: 40, height: 30, color: solidWhite}); 59 | for (var i = 0; i < 30; i++) { 60 | img1.setPixel(15,i, solidRed); 61 | } 62 | for (var i = 0; i < 40; i++) { 63 | img1.setPixel(i,15, solidRed); 64 | } 65 | var img2 = img1.resize({width: 16, height: 12, algorithm: "bezierInterpolation"}); 66 | var color = {}; 67 | 68 | //corners should be white 69 | expect(img2.getPixel( 1, 1,color)).toEqual(solidWhite); 70 | expect(img2.getPixel(14, 1,color)).toEqual(solidWhite); 71 | expect(img2.getPixel( 1,10,color)).toEqual(solidWhite); 72 | expect(img2.getPixel(14,10,color)).toEqual(solidWhite); 73 | 74 | // lines should be reddish off-white 75 | var offWhites = [ 76 | {x: 0,y: 6}, {x: 1,y: 6}, {x:8,y:6}, {x:14,y: 6}, {x:14,y: 6}, 77 | {x: 6,y: 0}, {x: 6,y: 1}, {x: 6,y:10}, {x: 6,y:11} 78 | ]; 79 | _.each(offWhites, function(coords){ 80 | color = img2.getPixel(coords.x,coords.y,color); 81 | expect(color.r).toEqual(255); 82 | expect(color.g).toBeLessThan(253); 83 | expect(color.b).toBeLessThan(253); 84 | }); 85 | }); 86 | 87 | it("resizes same aspect ratios", function() { 88 | var img1 = new Bitmap({width: 250, height: 250, color: solidWhite}); 89 | 90 | var img2a = img1.resize({width:150, height: 150, algorithm: "bezierInterpolation", fit: "pad"}); 91 | var img2b = img1.resize({width:150, height: 150, algorithm: "bezierInterpolation", fit: "crop"}); 92 | var img2c = img1.resize({width:150, height: 150, algorithm: "bezierInterpolation", fit: "stretch"}); 93 | 94 | var img3a = img1.resize({width:446, height: 446, algorithm: "bezierInterpolation", fit: "pad"}); 95 | var img3b = img1.resize({width:446, height: 446, algorithm: "bezierInterpolation", fit: "crop"}); 96 | var img3c = img1.resize({width:446, height: 446, algorithm: "bezierInterpolation", fit: "stretch"}); 97 | 98 | }); 99 | }); -------------------------------------------------------------------------------- /spec/rotate.spec.js: -------------------------------------------------------------------------------- 1 | var _ = require("underscore"); 2 | 3 | var ImageJS = require("../index"); 4 | var Bitmap = ImageJS.Bitmap; 5 | 6 | describe("Bitmap.resize", function() { 7 | var solidRed = {r: 255, g: 0, b: 0, a: 255}; 8 | var solidBlue = {r: 0, g: 0, b: 255, a: 255}; 9 | var solidGreen = {r: 0, g: 255, b: 0, a: 255}; 10 | var solidWhite = {r: 255, g: 255, b: 255, a: 255}; 11 | 12 | it("rotates 30 degrees", function() { 13 | var img1 = new Bitmap({width: 100, height: 100, color: solidWhite }); 14 | 15 | // use _fill to create red and blue lines 16 | img1._fill(solidRed, 48, 48, 50, 4); 17 | img1._fill(solidBlue, 48, 48, 4, 50); 18 | 19 | var img2 = img1.rotate({degrees:30, fit: "same", padColor: solidGreen}); 20 | 21 | // fit:"same" => dimensions are the same 22 | expect(img2.width).toEqual(img1.width); 23 | expect(img2.height).toEqual(img1.height); 24 | 25 | // corners should be padded with green 26 | expect(img2.getPixel( 1, 1)).toEqual(solidGreen); 27 | expect(img2.getPixel(98, 1)).toEqual(solidGreen); 28 | expect(img2.getPixel( 1,98)).toEqual(solidGreen); 29 | expect(img2.getPixel(98,98)).toEqual(solidGreen); 30 | 31 | // where the red and blue lines were, should be white 32 | expect(img2.getPixel(60, 50)).toEqual(solidWhite); 33 | expect(img2.getPixel(70, 50)).toEqual(solidWhite); 34 | expect(img2.getPixel(80, 50)).toEqual(solidWhite); 35 | expect(img2.getPixel(90, 50)).toEqual(solidWhite); 36 | 37 | expect(img2.getPixel(50, 60)).toEqual(solidWhite); 38 | expect(img2.getPixel(50, 70)).toEqual(solidWhite); 39 | expect(img2.getPixel(50, 80)).toEqual(solidWhite); 40 | expect(img2.getPixel(50, 90)).toEqual(solidWhite); 41 | 42 | // red and blue lines should be rotated 43 | expect(img2.getPixel(60, 44)).toEqual(solidRed); 44 | expect(img2.getPixel(70, 38)).toEqual(solidRed); 45 | expect(img2.getPixel(80, 32)).toEqual(solidRed); 46 | expect(img2.getPixel(90, 27)).toEqual(solidRed); 47 | 48 | expect(img2.getPixel(56, 60)).toEqual(solidBlue); 49 | expect(img2.getPixel(62, 70)).toEqual(solidBlue); 50 | expect(img2.getPixel(68, 80)).toEqual(solidBlue); 51 | expect(img2.getPixel(73, 90)).toEqual(solidBlue); 52 | }); 53 | 54 | }); -------------------------------------------------------------------------------- /test/dimensions.js: -------------------------------------------------------------------------------- 1 | var BitmapJS = require("../index"); 2 | var Bitmap = BitmapJS.Bitmap; 3 | 4 | var filename = process.argv[2]; 5 | var width = parseInt(process.argv[3]); 6 | var height = parseInt(process.argv[4]); 7 | 8 | var bm = new Bitmap(); 9 | bm.readFile(filename) 10 | .then(function() { 11 | console.log(JSON.stringify({width: bm.width, height: bm.height})); 12 | }) 13 | .catch(function(error) { 14 | console.log(error.message); 15 | }); -------------------------------------------------------------------------------- /test/invert.js: -------------------------------------------------------------------------------- 1 | var BitmapJS = require("../index"); 2 | var Bitmap = BitmapJS.Bitmap; 3 | 4 | var filename = process.argv[2]; 5 | 6 | var bm = new Bitmap(); 7 | bm.readFile(filename) 8 | .then(function() { 9 | var neg = bm.negative(); 10 | var suffix = filename.substr(-4); 11 | var negFilename = filename.replace(suffix, '.neg' + suffix); 12 | return neg.writeFile(negFilename); 13 | }) 14 | .then(function() { 15 | console.log("Done"); 16 | }) 17 | .catch(function(error) { 18 | console.log(error.message); 19 | }); -------------------------------------------------------------------------------- /test/resize.js: -------------------------------------------------------------------------------- 1 | var BitmapJS = require("../index"); 2 | var Bitmap = BitmapJS.Bitmap; 3 | 4 | if (process.argv.length < 5) { 5 | console.log("Usage: node test/resize filename width height [algorithm] [fit]"); 6 | console.log(" algorithm: one of nearestNeighbour, bilinearInterpolation, bicubicInterpolation, bezierInterpolation"); 7 | console.log(" fit: one of pad, crop, stretch"); 8 | process.exit(0); 9 | } 10 | 11 | var filename = process.argv[2]; 12 | var width = parseInt(process.argv[3]); 13 | var height = parseInt(process.argv[4]); 14 | var algorithm = process.argv[5] || "nearestNeighbor"; 15 | var fit = process.argv[6] || "stretch"; 16 | var bm = new Bitmap(); 17 | var red = {r:255,g:0,b:0,a:255}; 18 | bm.readFile(filename) 19 | .then(function() { 20 | var bm2 = bm.resize({width: width, height: height, algorithm: algorithm, fit: fit, padColor: red}); 21 | 22 | var suffix = filename.substr(-4); 23 | var dims = '.' + width + 'x' + height; 24 | var filename2 = filename.replace(suffix, dims + suffix); 25 | return bm2.writeFile(filename2, {quality: 80}); 26 | }) 27 | .then(function() { 28 | console.log("Done"); 29 | }) 30 | .catch(function(error) { 31 | console.log(error.message); 32 | }); -------------------------------------------------------------------------------- /test/rotate.js: -------------------------------------------------------------------------------- 1 | var BitmapJS = require("../index"); 2 | var Bitmap = BitmapJS.Bitmap; 3 | 4 | if (process.argv.length < 4) { 5 | console.log("Usage: node test/rotate filename degrees [fit] [width] [height]"); 6 | console.log(" fit: one of pad, crop, same"); 7 | process.exit(0); 8 | } 9 | 10 | var filename = process.argv[2]; 11 | var degrees = parseInt(process.argv[3]); 12 | var fit = process.argv[4] || "same"; 13 | var width = parseInt(process.argv[5] || 0); 14 | var height = parseInt(process.argv[6] || 0); 15 | var bm = new Bitmap(); 16 | var red = {r:255,g:0,b:0,a:255}; 17 | bm.readFile(filename) 18 | .then(function() { 19 | if (!width || !height) { 20 | width = bm.width; 21 | height = bm.height; 22 | } 23 | var bm2 = bm.rotate({degrees: degrees, fit: fit, padColor: red, width: width, height: height}); 24 | 25 | var suffix = filename.substr(-4); 26 | var dims = '.r' + degrees; 27 | var filename2 = filename.replace(suffix, dims + suffix); 28 | return bm2.writeFile(filename2, {quality: 80}); 29 | }) 30 | .then(function() { 31 | console.log("Done"); 32 | }) 33 | .catch(function(error) { 34 | console.log(error.message); 35 | }); --------------------------------------------------------------------------------