mirror of
https://github.com/danbulant/pngjs
synced 2026-06-20 23:11:19 +00:00
Add writing 16 bit channels and support for grayscale input (#82)
* Add support for writing 16 bit channels * Fix for quick exit from the bitpacker * Add example and describe bitDepth option in README
This commit is contained in:
parent
a1d9b0a52d
commit
2c37ebcbf2
5 changed files with 156 additions and 41 deletions
|
|
@ -131,7 +131,10 @@ As input any color type is accepted (grayscale, rgb, palette, grayscale with alp
|
|||
- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`)
|
||||
- `filterType` - png filtering method for scanlines (default: -1 => auto, accepts array of numbers 0-4)
|
||||
- `colorType` - the output colorType - see constants. 0 = grayscale, no alpha, 2 = color, no alpha, 4 = grayscale & alpha, 6 = color & alpha. Default currently 6, but in the future may calculate best mode.
|
||||
- `inputHasAlpha` - whether the input bitmap has 4 bits per pixel (rgb and alpha) or 3 (rgb - no alpha).
|
||||
- `inputColorType` - the input colorType - see constants. Default is 6 (RGBA)
|
||||
- `bitDepth` - the bitDepth of the output, 8 or 16 bits. Input data is expected to have this bit depth.
|
||||
16 bit data is expected in the system endianness (Default: 8)
|
||||
- `inputHasAlpha` - whether the input bitmap has 4 bytes per pixel (rgb and alpha) or 3 (rgb - no alpha).
|
||||
- `bgColor` - an object containing red, green, and blue values between 0 and 255
|
||||
that is used when packing a PNG if alpha is not to be included (default: 255,255,255)
|
||||
|
||||
|
|
|
|||
51
examples/16bit_write.js
Normal file
51
examples/16bit_write.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var fs = require('fs');
|
||||
var PNG = require("../lib/png").PNG;
|
||||
var w = 32;
|
||||
var h = 64;
|
||||
|
||||
/// RGBA input (color type 6)
|
||||
var buffer = new Buffer(2 * w * h * 4);
|
||||
var bitmap = new Uint16Array(buffer.buffer);
|
||||
for (var i = 0; i < h; i++) {
|
||||
for (var j = 0; j < w; j++) {
|
||||
bitmap[i * 4 * w + 4*j] = i * 65535 / h;
|
||||
bitmap[i * 4 * w + 4*j + 1] = j * 65535 / w;
|
||||
bitmap[i * 4 * w + 4*j + 2] = (h-i) * 65535 / h;
|
||||
bitmap[i * 4 * w + 4*j + 3] = 65535;
|
||||
}
|
||||
}
|
||||
|
||||
var png = new PNG({
|
||||
width: w,
|
||||
height:h,
|
||||
bitDepth: 16,
|
||||
colorType: 6,
|
||||
inputColorType: 6,
|
||||
inputHasAlpha: true
|
||||
});
|
||||
|
||||
png.data = buffer;
|
||||
png.pack().pipe(fs.createWriteStream('colortype6.png'));
|
||||
|
||||
//////// Grayscale 16 bits///////
|
||||
|
||||
var buffer = new Buffer(2 * w * h);
|
||||
var bitmap = new Uint16Array(buffer.buffer);
|
||||
for (var i = 0; i < h; i++) {
|
||||
for (var j = 0; j < w; j++)
|
||||
bitmap[i * w + j] = i * 65535 / h;
|
||||
}
|
||||
|
||||
png = new PNG({
|
||||
width: w,
|
||||
height:h,
|
||||
bitDepth: 16,
|
||||
colorType: 0,
|
||||
inputColorType: 0,
|
||||
inputHasAlpha: false
|
||||
});
|
||||
|
||||
png.data = buffer;
|
||||
png.pack().pipe(fs.createWriteStream('colortype0.png'));
|
||||
127
lib/bitpacker.js
127
lib/bitpacker.js
|
|
@ -2,73 +2,124 @@
|
|||
|
||||
var constants = require('./constants');
|
||||
|
||||
module.exports = function(data, width, height, options) {
|
||||
module.exports = function(dataIn, width, height, options) {
|
||||
var outHasAlpha = [constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf(options.colorType) !== -1;
|
||||
|
||||
if ([constants.COLORTYPE_GRAYSCALE, constants.COLORTYPE_ALPHA].indexOf(options.colorType) === -1) {
|
||||
if (options.colorType === options.inputColorType) {
|
||||
var bigEndian = (function() {
|
||||
var buffer = new ArrayBuffer(2);
|
||||
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
|
||||
// Int16Array uses the platform's endianness.
|
||||
return new Int16Array(buffer)[0] !== 256;
|
||||
})();
|
||||
// If no need to convert to grayscale and alpha is present/absent in both, take a fast route
|
||||
if (options.inputHasAlpha && outHasAlpha) {
|
||||
return data;
|
||||
}
|
||||
if (!options.inputHasAlpha && !outHasAlpha) {
|
||||
return data;
|
||||
if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)){
|
||||
return dataIn;
|
||||
}
|
||||
}
|
||||
|
||||
// map to a UInt16 array if data is 16bit, fix endianness below
|
||||
var data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
|
||||
|
||||
var maxValue = 255;
|
||||
var inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType];
|
||||
var outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType];
|
||||
if (options.bitDepth === 16) {
|
||||
maxValue = 65535;
|
||||
outBpp *= 2;
|
||||
}
|
||||
var outData = new Buffer(width * height * outBpp);
|
||||
var inBpp = options.inputHasAlpha ? 4 : 3;
|
||||
|
||||
var inIndex = 0;
|
||||
var outIndex = 0;
|
||||
|
||||
var bgColor = options.bgColor || {};
|
||||
if (bgColor.red === undefined) {
|
||||
bgColor.red = 255;
|
||||
bgColor.red = maxValue;
|
||||
}
|
||||
if (bgColor.green === undefined) {
|
||||
bgColor.green = 255;
|
||||
bgColor.green = maxValue;
|
||||
}
|
||||
if (bgColor.blue === undefined) {
|
||||
bgColor.blue = 255;
|
||||
bgColor.blue = maxValue;
|
||||
}
|
||||
|
||||
function getRGBA(data, inIndex) {
|
||||
var red, green, blue, alpha = maxValue;
|
||||
switch (options.inputColorType) {
|
||||
case constants.COLORTYPE_COLOR_ALPHA:
|
||||
alpha = data[inIndex + 3];
|
||||
red = data[inIndex];
|
||||
green = data[inIndex+1];
|
||||
blue = data[inIndex+2];
|
||||
break;
|
||||
case constants.COLORTYPE_COLOR:
|
||||
red = data[inIndex];
|
||||
green = data[inIndex+1];
|
||||
blue = data[inIndex+2];
|
||||
break;
|
||||
case constants.COLORTYPE_ALPHA:
|
||||
alpha = data[inIndex + 1];
|
||||
red = data[inIndex];
|
||||
green = red;
|
||||
blue = red;
|
||||
break;
|
||||
case constants.COLORTYPE_GRAYSCALE:
|
||||
red = data[inIndex];
|
||||
green = red;
|
||||
blue = red;
|
||||
break;
|
||||
default:
|
||||
throw new Error('input color type:' + options.inputColorType + ' is not supported at present');
|
||||
}
|
||||
|
||||
if (options.inputHasAlpha) {
|
||||
if (!outHasAlpha) {
|
||||
alpha /= maxValue;
|
||||
red = Math.min(Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), maxValue);
|
||||
green = Math.min(Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), maxValue);
|
||||
blue = Math.min(Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), maxValue);
|
||||
}
|
||||
}
|
||||
return {red, green, blue, alpha};
|
||||
}
|
||||
|
||||
for (var y = 0; y < height; y++) {
|
||||
for (var x = 0; x < width; x++) {
|
||||
var red = data[inIndex];
|
||||
var green = data[inIndex + 1];
|
||||
var blue = data[inIndex + 2];
|
||||
|
||||
var alpha;
|
||||
if (options.inputHasAlpha) {
|
||||
alpha = data[inIndex + 3];
|
||||
if (!outHasAlpha) {
|
||||
alpha /= 255;
|
||||
red = Math.min(Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0), 255);
|
||||
green = Math.min(Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0), 255);
|
||||
blue = Math.min(Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0), 255);
|
||||
}
|
||||
}
|
||||
else {
|
||||
alpha = 255;
|
||||
}
|
||||
var rgba = getRGBA(data, inIndex);
|
||||
|
||||
switch (options.colorType) {
|
||||
case constants.COLORTYPE_COLOR_ALPHA:
|
||||
case constants.COLORTYPE_COLOR:
|
||||
outData[outIndex] = red;
|
||||
outData[outIndex + 1] = green;
|
||||
outData[outIndex + 2] = blue;
|
||||
if (outHasAlpha) {
|
||||
outData[outIndex + 3] = alpha;
|
||||
if (options.bitDepth === 8) {
|
||||
outData[outIndex] = rgba.red;
|
||||
outData[outIndex + 1] = rgba.green;
|
||||
outData[outIndex + 2] = rgba.blue;
|
||||
if (outHasAlpha) {
|
||||
outData[outIndex + 3] = rgba.alpha;
|
||||
}
|
||||
} else {
|
||||
outData.writeUInt16BE(rgba.red, outIndex);
|
||||
outData.writeUInt16BE(rgba.green, outIndex + 2);
|
||||
outData.writeUInt16BE(rgba.blue, outIndex + 4);
|
||||
if (outHasAlpha) {
|
||||
outData.writeUInt16BE(rgba.alpha, outIndex + 6);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case constants.COLORTYPE_ALPHA:
|
||||
case constants.COLORTYPE_GRAYSCALE:
|
||||
// Convert to grayscale and alpha
|
||||
var grayscale = (red + green + blue) / 3;
|
||||
outData[outIndex] = grayscale;
|
||||
if (outHasAlpha) {
|
||||
outData[outIndex + 1] = alpha;
|
||||
var grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
|
||||
if (options.bitDepth === 8) {
|
||||
outData[outIndex] = grayscale;
|
||||
if (outHasAlpha) {
|
||||
outData[outIndex + 1] = rgba.alpha;
|
||||
}
|
||||
} else {
|
||||
outData.writeUInt16BE(grayscale, outIndex);
|
||||
if (outHasAlpha) {
|
||||
outData.writeUInt16BE(rgba.alpha, outIndex + 2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,10 +152,12 @@ module.exports = function(pxData, width, height, options, bpp) {
|
|||
throw new Error('unrecognised filter types');
|
||||
}
|
||||
|
||||
if (options.bitDepth === 16) bpp *= 2;
|
||||
var byteWidth = width * bpp;
|
||||
var rawPos = 0;
|
||||
var pxPos = 0;
|
||||
var rawData = new Buffer((byteWidth + 1) * height);
|
||||
|
||||
var sel = filterTypes[0];
|
||||
|
||||
for (var y = 0; y < height; y++) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ var Packer = module.exports = function(options) {
|
|||
options.bitDepth = options.bitDepth || 8;
|
||||
// This is outputColorType
|
||||
options.colorType = (typeof options.colorType === 'number') ? options.colorType : constants.COLORTYPE_COLOR_ALPHA;
|
||||
options.inputColorType = options.inputColorType || constants.COLORTYPE_COLOR_ALPHA;
|
||||
options.inputColorType = (typeof options.inputColorType === 'number') ? options.inputColorType : constants.COLORTYPE_COLOR_ALPHA;
|
||||
|
||||
if ([
|
||||
constants.COLORTYPE_GRAYSCALE,
|
||||
|
|
@ -27,7 +27,15 @@ var Packer = module.exports = function(options) {
|
|||
].indexOf(options.colorType) === -1) {
|
||||
throw new Error('option color type:' + options.colorType + ' is not supported at present');
|
||||
}
|
||||
if (options.bitDepth !== 8) {
|
||||
if ([
|
||||
constants.COLORTYPE_GRAYSCALE,
|
||||
constants.COLORTYPE_COLOR,
|
||||
constants.COLORTYPE_COLOR_ALPHA,
|
||||
constants.COLORTYPE_ALPHA
|
||||
].indexOf(options.inputColorType) === -1) {
|
||||
throw new Error('option input color type:' + options.inputColorType + ' is not supported at present');
|
||||
}
|
||||
if (options.bitDepth !== 8 && options.bitDepth !== 16) {
|
||||
throw new Error('option bit depth:' + options.bitDepth + ' is not supported at present');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue