Comparing version 0.0.1 to 0.0.2
@@ -31,2 +31,3 @@ /** | ||
//var png = require("png-js"); | ||
var PNG = require("node-png").PNG; | ||
@@ -37,2 +38,7 @@ var Enums = require("./enums"); | ||
// default pad colour | ||
var transparentBlack = { | ||
r: 0, g: 0, b: 0, a: 0 | ||
}; | ||
var Bitmap = module.exports = function(options) { | ||
@@ -79,3 +85,16 @@ if (options) { | ||
read: function(stream, type) { | ||
_deduceFileType: function(filename) { | ||
switch (filename.substr(-4).toLowerCase()) { | ||
case ".jpg": | ||
return Enums.ImageType.JPG; | ||
case ".png": | ||
return Enums.ImageType.PNG; | ||
} | ||
if (filename.substr(-5).toLowerCase() == ".jpeg") { | ||
return Enums.ImageType.JPG; | ||
} | ||
throw new Error("Can't recognise image type: " + filename); | ||
}, | ||
_readStream: function(stream) { | ||
var self = this; | ||
@@ -90,28 +109,48 @@ var deferred = Promise.defer(); | ||
var data = Buffer.concat(chunks); | ||
if (data) { | ||
try { | ||
switch(type) { | ||
case Enums.ImageType.JPG: | ||
self._data = jpeg.decode(data); | ||
break; | ||
//case Enums.ImageType.PNG: | ||
// self._data = png.decode(data); | ||
// break; | ||
default: | ||
throw new Error("Not supported: ImageType " + type); | ||
} | ||
//console.log(JSON.stringify({width: self._data.width, height: self._data.height, data: (self._data.data != undefined)})); | ||
deferred.resolve(); | ||
} | ||
catch(ex) { | ||
deferred.reject(ex); | ||
} | ||
} else { | ||
deferred.reject(new Error("No data found on stream")) | ||
} | ||
deferred.resolve(data); | ||
}); | ||
stream.on('error', function(error) { | ||
deferred.reject(error); | ||
}); | ||
return deferred.promise; | ||
}, | ||
_readPNG: function(stream) { | ||
var deferred = Promise.defer(); | ||
var png = new PNG({filterType: 4}); | ||
png.on('parsed', function() { | ||
deferred.resolve(png); | ||
}); | ||
png.on('error', function(error) { | ||
deferred.rejecyt(error); | ||
}); | ||
stream.pipe(png); | ||
return deferred.promise; | ||
}, | ||
read: function(stream, type) { | ||
var self = this; | ||
switch(type) { | ||
case Enums.ImageType.JPG: | ||
return this._readStream(stream) | ||
.then(function(data) { | ||
self._data = jpeg.decode(data); | ||
}); | ||
case Enums.ImageType.PNG: | ||
return this._readPNG(stream) | ||
.then(function(png) { | ||
self._data = { | ||
data: png.data, | ||
width: png.width, | ||
height: png.height | ||
}; | ||
}); | ||
default: | ||
return Promise.reject(new Error("Not supported: ImageType " + type)); | ||
} | ||
}, | ||
readFile: function(filename, type) { | ||
type = type || this._deduceFileType(filename); | ||
var stream = fs.createReadStream(filename); | ||
@@ -124,22 +163,31 @@ return this.read(stream, type); | ||
try { | ||
var buffer; | ||
stream.on('finish', function() { | ||
deferred.resolve(); | ||
}); | ||
stream.on('error', function(error) { | ||
deferred.reject(error); | ||
}); | ||
switch(type) { | ||
case Enums.ImageType.JPG: | ||
buffer = jpeg.encode(this._data, 50).data; | ||
var buffer = jpeg.encode(this._data, 50).data; | ||
stream.write(buffer); | ||
stream.end(); | ||
break; | ||
//case Enums.ImageType.PNG: | ||
// buffer = png.encode(this._data); | ||
// break; | ||
case Enums.ImageType.PNG: | ||
var png = new PNG(); | ||
png.width = this.width; | ||
png.height = this.height; | ||
png.data = this._data.data; | ||
png.on('end', function() { | ||
deferred.resolve(); | ||
}); | ||
png.on('error', function(error) { | ||
deferred.reject(error); | ||
}); | ||
png.pack().pipe(stream); | ||
break; | ||
default: | ||
throw new Error("Not supported: ImageType " + type); | ||
} | ||
stream.on('finish', function() { | ||
deferred.resolve(); | ||
}); | ||
stream.on('error', function(error) { | ||
deferred.reject(error); | ||
}); | ||
stream.write(buffer); | ||
stream.end(); | ||
} | ||
@@ -152,2 +200,3 @@ catch(ex) { | ||
writeFile: function(filename, type) { | ||
type = type || this._deduceFileType(filename); | ||
var stream = fs.createWriteStream(filename); | ||
@@ -184,5 +233,57 @@ return this.write(stream, type); | ||
var that = new Bitmap(options); | ||
Resize[options.algorithm](this, that); | ||
// crop determines from where in src to fetch image data | ||
options.crop = { | ||
left: 0, top: 0, | ||
width: this.width, height: this.height | ||
}; | ||
// bounds determines where in dst to draw the scaled image | ||
options.bounds = { | ||
left: 0, top: 0, | ||
width: options.width, height: options.height | ||
}; | ||
switch (options.fit) { | ||
case "pad": // fit all of src in dst with optional pad colour | ||
options.padColor = options.padColor || transparentBlack; | ||
var srcAr = this.width / this.height; | ||
var dstAr = that.width / that.height; | ||
var w2 = Math.round(srcAr * that.height); | ||
var h2 = Math.round(that.width / srcAr); | ||
if (w2 < that.width) { | ||
// pad sides | ||
var dw = that.width - w2; | ||
options.bounds.left = Math.round(dw / 2); | ||
options.bounds.width = w2; | ||
} else if (h2 < that.height) { | ||
// pad top & bottom | ||
var dh = that.height - h2; | ||
options.bounds.top = Math.round(dh / 2); | ||
options.bounds.height = h2; | ||
} | ||
break; | ||
case "crop": // crop original to fit in dst with no pad | ||
var gravity = options.gravity || {x: 0.5, y: 0.5}; | ||
var dstAr = that.width / that.height; | ||
var w2 = Math.round(dstAr * this.height); | ||
var h2 = Math.round(this.width / dstAr); | ||
if (w2 < this.width) { | ||
// crop src width | ||
var dw = this.width - w2; | ||
options.crop.left = Math.round(gravity.x * dw); | ||
options.crop.width = w2; | ||
} else if (h2 < this.height) { | ||
// crop src height | ||
var dh = this.height - h2; | ||
options.crop.top = Math.round(gravity.y * dh); | ||
options.crop.height = h2; | ||
} | ||
break; | ||
case "stretch": | ||
default: | ||
// default crop and bounds are set above | ||
break; | ||
} | ||
Resize[options.algorithm](this, that, options); | ||
return that; | ||
} | ||
} |
@@ -30,23 +30,179 @@ /** | ||
module.exports = { | ||
nearestNeighbor: function(src, dst) { | ||
var wSrc = src.width; | ||
var hSrc = src.height; | ||
var wDst = dst.width; | ||
var hDst = dst.height; | ||
_pad: function(dst, options) { | ||
var top = options.bounds.top; | ||
var left = options.bounds.left; | ||
var bottom = top + options.bounds.height; | ||
var right = left + options.bounds.width; | ||
// optimization - quit now if top and left are zero | ||
if (!top && !left) { | ||
return; | ||
} | ||
var width = dst.width; | ||
var height = dst.height; | ||
var data = dst._data.data; | ||
var r = options.padColor.r; | ||
var g = options.padColor.g; | ||
var b = options.padColor.b; | ||
var a = options.padColor.a; | ||
var i, j, pos; | ||
// top margin | ||
for (i = 0; i < top; i++) { | ||
for (j = 0; j < width; j++) { | ||
pos = (i * width + j) * 4; | ||
data[pos++] = r; | ||
data[pos++] = g; | ||
data[pos++] = b; | ||
data[pos++] = a; | ||
} | ||
} | ||
// bottom margin | ||
for (i = bottom; i < height; i++) { | ||
for (j = 0; j < width; j++) { | ||
pos = (i * width + j) * 4; | ||
data[pos++] = r; | ||
data[pos++] = g; | ||
data[pos++] = b; | ||
data[pos++] = a; | ||
} | ||
} | ||
// left margin | ||
for (i = 0; i < height; i++) { | ||
for (j = 0; j < left; j++) { | ||
pos = (i * width + j) * 4; | ||
data[pos++] = r; | ||
data[pos++] = g; | ||
data[pos++] = b; | ||
data[pos++] = a; | ||
} | ||
} | ||
// right margin | ||
for (i = 0; i < height; i++) { | ||
for (j = right; j < width; j++) { | ||
pos = (i * width + j) * 4; | ||
data[pos++] = r; | ||
data[pos++] = g; | ||
data[pos++] = b; | ||
data[pos++] = a; | ||
} | ||
} | ||
}, | ||
nearestNeighbor: function(src, dst, options) { | ||
this._pad(dst, options); | ||
var srcWidth = src.width; | ||
var srcHeight = src.height; | ||
var dstWidth = dst.width; | ||
var dstHeight = dst.height; | ||
var tSrc = options.crop.top; | ||
var lSrc = options.crop.left; | ||
var wSrc = options.crop.width; | ||
var hSrc = options.crop.height; | ||
var bSrc = tSrc + hSrc; | ||
var rSrc = lSrc + wSrc; | ||
//console.log("tSrc="+tSrc + ", lSrc="+lSrc + ", wSrc="+wSrc + ", hSrc="+hSrc + ", bSrc="+bSrc + ", rSrc="+rSrc); | ||
var tDst = options.bounds.top; | ||
var lDst = options.bounds.left; | ||
var wDst = options.bounds.width; | ||
var hDst = options.bounds.height; | ||
var bDst = tDst + hDst; | ||
var rDst = lDst + wDst; | ||
//console.log("tDst="+tDst + ", lDst="+lDst + ", wDst="+wDst + ", hDst="+hDst + ", bDst="+bDst + ", rDst="+rDst); | ||
var bufSrc = src._data.data; | ||
var bufDst = dst._data.data; | ||
for (var i = 0; i < hDst; i++) { | ||
for (var j = 0; j < wDst; j++) { | ||
var pos = (i * wDst + j) * 4; | ||
var nni = Math.round(i * hSrc / hDst); | ||
var nnj = Math.round(j * wSrc / wDst); | ||
var nnPos = (nni * wSrc + nnj) * 4; | ||
for (var i = tDst; i < bDst; i++) { | ||
for (var j = lDst; j < rDst; j++) { | ||
var posDst = (i * dstWidth + j) * 4; | ||
var iSrc = tSrc + Math.round((i-tDst) * hSrc / hDst); | ||
var jSrc = lSrc + Math.round((j-lDst) * wSrc / wDst); | ||
var posSrc = (iSrc * srcWidth + jSrc) * 4; | ||
bufDst[pos++] = bufSrc[nnPos++]; | ||
bufDst[pos++] = bufSrc[nnPos++]; | ||
bufDst[pos++] = bufSrc[nnPos++]; | ||
bufDst[pos++] = bufSrc[nnPos++]; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
} | ||
} | ||
}, | ||
bilinearInterpolation: function(src, dst, options) { | ||
this._pad(dst, options); | ||
var srcWidth = src.width; | ||
var srcHeight = src.height; | ||
var dstWidth = dst.width; | ||
var dstHeight = dst.height; | ||
var tSrc = options.crop.top; | ||
var lSrc = options.crop.left; | ||
var wSrc = options.crop.width; | ||
var hSrc = options.crop.height; | ||
var bSrc = tSrc + hSrc; | ||
var rSrc = lSrc + wSrc; | ||
//console.log("tSrc="+tSrc + ", lSrc="+lSrc + ", wSrc="+wSrc + ", hSrc="+hSrc + ", bSrc="+bSrc + ", rSrc="+rSrc); | ||
var tDst = options.bounds.top; | ||
var lDst = options.bounds.left; | ||
var wDst = options.bounds.width; | ||
var hDst = options.bounds.height; | ||
var bDst = tDst + hDst; | ||
var rDst = lDst + wDst; | ||
//console.log("tDst="+tDst + ", lDst="+lDst + ", wDst="+wDst + ", hDst="+hDst + ", bDst="+bDst + ", rDst="+rDst); | ||
var bufSrc = src._data.data; | ||
var bufDst = dst._data.data; | ||
var interpolate = function(k, kMin, vMin, kMax, vMax) { | ||
// special case - k is integer | ||
if (kMin === kMax) { | ||
return vMin; | ||
} | ||
return Math.round((k - kMin) * vMax + (kMax - k) * vMin); | ||
}; | ||
var assign = function(pos, offset, x, xMin, xMax, y, yMin, yMax) { | ||
var posMin = (yMin * srcWidth + xMin) * 4 + offset; | ||
var posMax = (yMin * srcWidth + xMax) * 4 + offset; | ||
var vMin = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]); | ||
// special case, y is integer | ||
if (yMax === yMin) { | ||
bufDst[pos+offset] = vMin; | ||
} else { | ||
posMin = (yMax * srcWidth + xMin) * 4 + offset; | ||
posMax = (yMax * srcWidth + xMax) * 4 + offset; | ||
var vMax = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]); | ||
bufDst[pos+offset] = interpolate(y, yMin, vMin, yMax, vMax); | ||
} | ||
} | ||
for (var i = tDst; i < bDst; i++) { | ||
for (var j = lDst; j < rDst; j++) { | ||
var posDst = (i * dstWidth + j) * 4; | ||
// x & y in src coordinates | ||
var x = lSrc + (j-lDst) * wSrc / wDst; | ||
var xMin = Math.floor(x); | ||
var xMax = Math.min(Math.ceil(x), rSrc-1); | ||
var y = tSrc + (i-tDst) * hSrc / hDst; | ||
var yMin = Math.floor(y); | ||
var yMax = Math.min(Math.ceil(y), bSrc-1); | ||
assign(posDst, 0, x, xMin, xMax, y, yMin, yMax); | ||
assign(posDst, 1, x, xMin, xMax, y, yMin, yMax); | ||
assign(posDst, 2, x, xMin, xMax, y, yMin, yMax); | ||
assign(posDst, 3, x, xMin, xMax, y, yMin, yMax); | ||
} | ||
} | ||
} | ||
} |
{ | ||
"name": "imagejs", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "Image Processor", | ||
@@ -22,2 +22,3 @@ "private": false, | ||
"jpeg-js": "0.1.1", | ||
"node-png": "0.4.3", | ||
"underscore": "1.4.4" | ||
@@ -24,0 +25,0 @@ }, |
@@ -13,2 +13,23 @@ # ImageJS | ||
# New Features! | ||
<ul> | ||
<li> | ||
<a href="#image-resize">Enhanced Resize</a> | ||
<ul> | ||
<li>New Resize Algorithm: Bilinear Interpolation</li> | ||
<li>Stretch, Crop or Pad to Fit</li> | ||
</ul> | ||
</li> | ||
<li><a href="#reading-images">PNG Image files supported</a></li> | ||
</ul> | ||
# Coming Soon | ||
<ul> | ||
<li>Bicubic Interpolation</li> | ||
<li>Crop</li> | ||
<li>Pad</li> | ||
</ul> | ||
# Contents | ||
@@ -21,3 +42,8 @@ | ||
<li><a href="#creating-bitmaps">Creating Bitmaps</a></li> | ||
<li><a href="#manipulating-bitmaps">Manipulating Bitmaps</a></li> | ||
<li> | ||
<a href="#manipulating-bitmaps">Manipulating Bitmaps</a> | ||
<ul> | ||
<li><a href="set-pixel">Set Pixel</a></li> | ||
</ul> | ||
</li> | ||
<li><a href="#reading-images">Reading Images</a></li> | ||
@@ -60,2 +86,3 @@ <li><a href="#writing-images">Writing Images</a></li> | ||
```javascript | ||
// Set a pixel | ||
@@ -69,5 +96,31 @@ // where: 0 <= x < width, 0 <= y < height, 0 <= a,r,g,b < 256 | ||
// Create a new bitmap resized from an original | ||
// Currently only nearest neighbor is implemented. More to follow. | ||
var thumbnail = bitmap.resize({width: 64, height: 64, algorithm: "nearestNeighbor"}) | ||
// resize to 64x64 icon sized bitmap using nearest neighbor algorithm & stretch to fit | ||
var thumbnail = bitmap.resize({ | ||
width: 64, height: 64, | ||
algorithm: "nearestNeighbor" | ||
}); | ||
// resize to 100x150 bitmap using bilinear interpolation and cropping to fit, gravity center | ||
var thumbnail = bitmap.resize({ | ||
width: 100, height: 150, | ||
algorithm: "bilinearInterpolation", | ||
fit: "crop", | ||
gravity: {x:0.5, y:0.5} // center - note: this is the default | ||
}); | ||
// resize to 300x200 bitmap using bilinear interpolation and padding to fit, pad color solid red | ||
var thumbnail = bitmap.resize({ | ||
width: 100, height: 150, | ||
algorithm: "bilinearInterpolation", | ||
fit: "pad", | ||
padColor: {r:255, g:0, b:0, a:255} | ||
}); | ||
``` | ||
**Supported Resize Algorithms** | ||
* nearestNeighbor | ||
* bilinearInterpolation | ||
## Reading Images | ||
@@ -78,3 +131,3 @@ | ||
var bitmap = new Bitmap(); | ||
bitmap.readFile(filename, ImageJS.ImageType.JPG) | ||
bitmap.readFile(filename) | ||
.then(function() { | ||
@@ -84,3 +137,3 @@ // bitmap is ready | ||
// read from stream | ||
// read JPG data from stream | ||
var stream = createReadStream(); | ||
@@ -99,3 +152,3 @@ var bitmap = new Bitmap(); | ||
// write to a file | ||
return bitmap.writeFile(filename, ImageJS.ImageType.JPG) | ||
return bitmap.writeFile(filename) | ||
.then(function() { | ||
@@ -105,5 +158,5 @@ // bitmap has been saved | ||
// write to a stream | ||
// write PNG Image to a stream | ||
var stream = createWriteStream(); | ||
return bitmap.write(stream, ImageJS.ImageType.JPG) | ||
return bitmap.write(stream, ImageJS.ImageType.PNG) | ||
.then(function() { | ||
@@ -121,2 +174,3 @@ // bitmap has been written and stream ended | ||
| 0.0.1 | Initial Version | | ||
| 0.0.2 | <ul><li><a href="#image-resize">Enhanced Resize</a><ul><li>New Resize Algorithm: Bilinear Interpolation</li><li>Stretch, Crop or Pad to Fit</li></ul></li><li><a href="#reading-images">PNG Image files supported</a></li></ul> | | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
26492
494
168
4
+ Addednode-png@0.4.3
+ Addednode-png@0.4.3(transitive)