├── .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 |
20 | - Graphics Object (draw and fill lines and shapes)
21 |
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 | - Internal Restructuring
- Corrected Documentation
- Better Bitmap Construction
- Performance Improvements
|
235 | | 0.0.8 | - Bug Fixes
- readFile this bug
- resize same aspect ratio fix
|
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 | });
--------------------------------------------------------------------------------