Compare commits

..

No commits in common. "master" and "0.4.3" have entirely different histories.

243 changed files with 1370 additions and 8273 deletions

View file

@ -1,2 +0,0 @@
/browser.js
/test/imagediff.js

View file

@ -1,19 +0,0 @@
{
"parserOptions": { "ecmaVersion": 2018 },
"extends": ["eslint:recommended", "prettier"],
"env": {
"node": true,
"es6": true
},
"rules": {
"no-var": "error"
},
"overrides": [
{
"files": ["test/*.js", "examples/*.js"],
"rules": {
"no-console": "off"
}
}
]
}

5
.gitignore vendored
View file

@ -1,6 +1 @@
node_modules
.idea
coverage
examples/*.png
browser\.js
.nyc_output

View file

@ -1,5 +0,0 @@
test
examples
.travis.yml
appveyor.yml

View file

@ -1,3 +0,0 @@
/browser.js
/test/imagediff.js
.nyc_output

View file

@ -1,9 +0,0 @@
language: node_js
after_success:
- if [ "$TRAVIS_NODE_VERSION" = "10" ]; then yarn coverage && yarn codecov; fi
node_js:
- "10"
- "12"
branches:
only:
- master

View file

@ -1,5 +1,4 @@
pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors
pngjs derived work Copyright (c) 2012 Kuba Niegowski
Copyright (c) 2012 Kuba Niegowski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

399
README.md
View file

@ -1,411 +1,150 @@
[![Build Status](https://travis-ci.com/lukeapage/pngjs.svg?branch=master)](https://travis-ci.com/lukeapage/pngjs) [![Build status](https://ci.appveyor.com/api/projects/status/qo5x8ayutr028108/branch/master?svg=true)](https://ci.appveyor.com/project/lukeapage/pngjs/branch/master) [![codecov](https://codecov.io/gh/lukeapage/pngjs/branch/master/graph/badge.svg)](https://codecov.io/gh/lukeapage/pngjs) [![npm version](https://badge.fury.io/js/pngjs.svg)](http://badge.fury.io/js/pngjs)
# pngjs
*This is a fork of pngjs to make it work with deno*
Simple PNG encoder/decoder for ~~Node.js~~ Deno with no dependencies.
Based on the original [pngjs](https://github.com/niegowski/node-pngjs) with the follow enhancements.
- Support for reading 1,2,4 & 16 bit files
- Support for reading interlace files
- Support for reading `tTRNS` transparent colours
- Support for writing colortype 0 (grayscale), colortype 2 (RGB), colortype 4 (grayscale alpha) and colortype 6 (RGBA)
- Sync interface as well as async
- API compatible with pngjs and node-pngjs
Known lack of support for:
- Extended PNG e.g. Animation
- Writing in colortype 3 (indexed color)
# Table of Contents
- [Requirements](#requirements)
- [Comparison Table](#comparison-table)
- [Tests](#tests)
- [Installation](#installation)
- [Browser](#browser)
- [Example](#example)
- [Async API](#async-api)
- [Sync API](#sync-api)
- [Changelog](#changelog)
# Comparison Table
| Name | Forked From | Sync | Async | 16 Bit | 1/2/4 Bit | Interlace | Gamma | Encodes | Tested |
| ------------- | ----------- | ---- | ----- | ------ | --------- | --------- | ------ | ------- | ------ |
| pngjs | | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| node-png | pngjs | No | Yes | No | No | No | Hidden | Yes | Manual |
| png-coder | pngjs | No | Yes | Yes | No | No | Hidden | Yes | Manual |
| pngparse | | No | Yes | No | Yes | No | No | No | Yes |
| pngparse-sync | pngparse | Yes | No | No | Yes | No | No | No | Yes |
| png-async | | No | Yes | No | No | No | No | Yes | Yes |
| png-js | | No | Yes | No | No | No | No | No | No |
Native C++ node decoders:
- png
- png-sync (sync version of above)
- pixel-png
- png-img
# Tests
Tested using [PNG Suite](http://www.schaik.com/pngsuite/). We read every file into pngjs, output it in standard 8bit colour, synchronously and asynchronously, then compare the original with the newly saved images.
To run the tests, fetch the repo (tests are not distributed via npm) and install with `npm i`, run `npm test`.
The only thing not converted is gamma correction - this is because multiple vendors will do gamma correction differently, so the tests will have different results on different browsers.
# Installation
About
========
Simple PNG encoder/decoder for Node.js with no native dependencies.
Installation
===============
```
$ npm install pngjs --save
$ npm install node-png
```
# Browser
The package has been build with a [Browserify](browserify.org) version (`npm run browserify`) and you can use the browser version by including in your code:
```
import { PNG } from 'pngjs/browser';
```
# Example
Example
==========
```js
var fs = require("fs"),
PNG = require("pngjs").PNG;
var fs = require('fs'),
PNG = require('pngjs').PNG;
fs.createReadStream("in.png")
.pipe(
new PNG({
filterType: 4,
})
)
.on("parsed", function () {
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
fs.createReadStream('in.png')
.pipe(new PNG({
filterType: 4
}))
.on('parsed', function() {
// invert color
this.data[idx] = 255 - this.data[idx];
this.data[idx + 1] = 255 - this.data[idx + 1];
this.data[idx + 2] = 255 - this.data[idx + 2];
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
// and reduce opacity
this.data[idx + 3] = this.data[idx + 3] >> 1;
}
}
// invert color
this.data[idx] = 255 - this.data[idx];
this.data[idx+1] = 255 - this.data[idx+1];
this.data[idx+2] = 255 - this.data[idx+2];
this.pack().pipe(fs.createWriteStream("out.png"));
});
// and reduce opacity
this.data[idx+3] = this.data[idx+3] >> 1;
}
}
this.pack().pipe(fs.createWriteStream('out.png'));
});
```
For more examples see `examples` folder.
# Async API
Documentation
================
As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported.
## Class: PNG
### Supported ancillary chunks
- `gAMA` - gamma,
- `tRNS` - transparency (but only for paletted image)
## Class: PNG
`PNG` is readable and writable `Stream`.
### Options
### Options
- `width` - use this with `height` if you want to create png from scratch
- `height` - as above
- `checkCRC` - whether parser should be strict about checksums in source stream (default: `true`)
- `deflateChunkSize` - chunk size used for deflating data chunks, this should be power of 2 and must not be less than 256 and more than 32\*1024 (default: 32 kB)
- `deflateLevel` - compression level for deflate (default: 9)
- `deflateStrategy` - compression strategy for deflate (default: 3)
- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`)
- `deflateChunkSize` - chunk size used for deflating data chunks, this should be power of 2 and must not be less than 256 and more than 32*1024 (default: 32 kB)
- `deflateLevel` - compression level for delate (default: 9)
- `deflateStrategy` - compression strategy for delate (default: 3)
- `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.
- `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)
### Event "metadata"
`function(metadata) { }`
Image's header has been parsed, metadata contains this information:
- `width` image size in pixels
- `height` image size in pixels
- `palette` image is paletted
- `color` image is not grayscale
- `alpha` image contains alpha channel
- `interlace` image is interlaced
### Event: "parsed"
`function(data) { }`
Input image has been completely parsed, `data` is complete and ready for modification.
Input image has been completly parsed, `data` is complete and ready for modification.
### Event: "error"
`function(error) { }`
### png.parse(data, [callback])
Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG.
### png.parse(data, [callback])
Parses PNG file data. Alternatively you can stream data to instance of PNG.
Optional `callback` is once called on `error` or `parsed`. The callback gets
two arguments `(err, data)`.
Returns `this` for method chaining.
#### Example
```js
new PNG({ filterType: 4 }).parse(imageData, function (error, data) {
console.log(error, data);
});
```
### png.pack()
Starts converting data to PNG file Stream.
Returns `this` for method chaining.
### png.bitblt(dst, sx, sy, w, h, dx, dy)
Helper for image manipulation, copies a rectangle of pixels from current (i.e. the source) image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`).
### png.bitblt(dst, sx, sy, w, h, dx, dy)
Helper for image manipulation, copies rectangle of pixels from current image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`).
Returns `this` for method chaining.
For example, the following code copies the top-left 100x50 px of `in.png` into dst and writes it to `out.png`:
```js
var dst = new PNG({ width: 100, height: 50 });
fs.createReadStream("in.png")
.pipe(new PNG())
.on("parsed", function () {
this.bitblt(dst, 0, 0, 100, 50, 0, 0);
dst.pack().pipe(fs.createWriteStream("out.png"));
});
```
### Property: adjustGamma()
Helper that takes data and adjusts it to be gamma corrected. Note that it is not 100% reliable with transparent colours because that requires knowing the background colour the bitmap is rendered on to.
In tests against PNG suite it compared 100% with chrome on all 8 bit and below images. On IE there were some differences.
The following example reads a file, adjusts the gamma (which sets the gamma to 0) and writes it out again, effectively removing any gamma correction from the image.
```js
fs.createReadStream("in.png")
.pipe(new PNG())
.on("parsed", function () {
this.adjustGamma();
this.pack().pipe(fs.createWriteStream("out.png"));
});
```
### Property: width
Width of image in pixels
### Property: height
### Property: height
Height of image in pixels
### Property: data
### Property: data
Buffer of image pixel data. Every pixel consists 4 bytes: R, G, B, A (opacity).
### Property: gamma
### Property: gamma
Gamma of image (0 if not specified)
## Packing a PNG and removing alpha (RGBA to RGB)
Changelog
============
When removing the alpha channel from an image, there needs to be a background color to correctly
convert each pixel's transparency to the appropriate RGB value. By default, pngjs will flatten
the image against a white background. You can override this in the options:
### 0.4.0 - Jun 05 2013
- fixed reading of destroyed input stream
```js
var fs = require("fs"),
PNG = require("pngjs").PNG;
### 0.4.0-alpha - 29 Nov 2012
- added zlib deflateStrategy option, default to Z_RLE (by pdpotter)
- added possibility to use multiple filters (by pdpotter, modified by niegowski)
fs.createReadStream("in.png")
.pipe(
new PNG({
colorType: 2,
bgColor: {
red: 0,
green: 255,
blue: 0,
},
})
)
.on("parsed", function () {
this.pack().pipe(fs.createWriteStream("out.png"));
});
```
### 0.3.0-alpha - 23 Aug 2012
- Processing data as Streams, not complete Buffers of data
# Sync API
### 0.2.0-alpha - 21 Aug 2012
- Input added palette, grayscale, no alpha support
- Better scanline filter selection
## PNG.sync
### 0.1.0-alpha - 19 Aug 2012
- First version
### PNG.sync.read(buffer)
Take a buffer and returns a PNG image. The properties on the image include the meta data and `data` as per the async API above.
```
var data = fs.readFileSync('in.png');
var png = PNG.sync.read(data);
```
### PNG.sync.write(png)
Take a PNG image and returns a buffer. The properties on the image include the meta data and `data` as per the async API above.
```
var data = fs.readFileSync('in.png');
var png = PNG.sync.read(data);
var options = { colorType: 6 };
var buffer = PNG.sync.write(png, options);
fs.writeFileSync('out.png', buffer);
```
### PNG.adjustGamma(src)
Adjusts the gamma of a sync image. See the async adjustGamma.
```
var data = fs.readFileSync('in.png');
var png = PNG.sync.read(data);
PNG.adjustGamma(png);
```
# Changelog
### 5.0.0 - 15/04/2020
- Drop support for Node 8
- Browserified bundle may now contain ES20(15-20) code if the supported node version supports it. Please run the browserified version through babel if you need to support older browsers.
### 4.0.1 - 15/04/2020
- Fix to possible null reference in nextTick of async method
### 4.0.0 - 09/04/2020
- Fix issue in newer nodes with using Buffer
- Fix async issue with some png files
- Drop support for Node 4 & 6
### 3.4.0 - 09/03/2019
- Include whether the png has alpha in the meta data
- emit an error if the image is truncated instead of hanging
- Add a browserified version
- speed up some mapping functions
### 3.3.3 - 19/04/2018
- Real fix for node 9
### 3.3.2 - 16/02/2018
- Fix for node 9
### 3.3.1 - 15/11/2017
- Bugfixes and removal of es6
### 3.3.0
- Add writing 16 bit channels and support for grayscale input
### 3.2.0 - 30/04/2017
- Support for encoding 8-bit grayscale images
### 3.1.0 - 30/04/2017
- Support for pngs with zlib chunks that are malformed after valid data
### 3.0.1 - 16/02/2017
- Fix single pixel pngs
### 3.0.0 - 03/08/2016
- Drop support for node below v4 and iojs. Pin to 2.3.0 to use with old, unsupported or patched node versions.
### 2.3.0 - 22/04/2016
- Support for sync in node 0.10
### 2.2.0 - 04/12/2015
- Add sync write api
- Fix newfile example
- Correct comparison table
### 2.1.0 - 28/10/2015
- rename package to pngjs
- added 'bgColor' option
### 2.0.0 - 08/10/2015
- fixes to readme
- _breaking change_ - bitblt on the png prototype now doesn't take a unused, unnecessary src first argument
### 1.2.0 - 13/09/2015
- support passing colorType to write PNG's and writing bitmaps without alpha information
### 1.1.0 - 07/09/2015
- support passing a deflate factory for controlled compression
### 1.0.2 - 22/08/2015
- Expose all PNG creation info
### 1.0.1 - 21/08/2015
- Fix non square interlaced files
### 1.0.0 - 08/08/2015
- More tests
- source linted
- maintainability refactorings
- async API - exceptions in reading now emit warnings
- documentation improvement - sync api now documented, adjustGamma documented
- breaking change - gamma chunk is now written. previously a read then write would destroy gamma information, now it is persisted.
### 0.0.3 - 03/08/2015
- Error handling fixes
- ignore files for smaller npm footprint
### 0.0.2 - 02/08/2015
- Bugfixes to interlacing, support for transparent colours
### 0.0.1 - 02/08/2015
- Initial release, see pngjs for older changelog.
# License
License
=========
(The MIT License)
Copyright (c) 2012 Kuba Niegowski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights

View file

@ -1,28 +0,0 @@
version: "{build}"
clone_depth: 10
environment:
matrix:
- nodejs_version: "10"
- nodejs_version: "12"
install:
- ps: Install-Product node $env:nodejs_version
- npm install
build: off
test_script:
- node --version && npm --version
- npm test
cache:
- node_modules # local npm modules
matrix:
fast_finish: true
branches:
only:
- master

View file

@ -1,50 +0,0 @@
#!/usr/bin/env node
let fs = require("fs");
let PNG = require("../lib/png").PNG;
let w = 32;
let h = 64;
/// RGBA input (color type 6)
let buffer = Buffer.alloc(2 * w * h * 4);
let bitmap = new Uint16Array(buffer.buffer);
for (let i = 0; i < h; i++) {
for (let 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;
}
}
let 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///////
buffer = Buffer.alloc(2 * w * h);
bitmap = new Uint16Array(buffer.buffer);
for (let i = 0; i < h; i++) {
for (let 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"));

View file

@ -1,22 +1,26 @@
let fs = require("fs"),
PNG = require("../lib/png").PNG; // note require('pngjs') outside this project
fs.createReadStream("test/in/basi0g01.png")
.pipe(new PNG({}))
.on("parsed", function () {
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
let idx = (this.width * y + x) << 2;
var fs = require('fs'),
PNG = require('pngjs').PNG;
// invert color
this.data[idx] = 255 - this.data[idx];
this.data[idx + 1] = 255 - this.data[idx + 1];
this.data[idx + 2] = 255 - this.data[idx + 2];
fs.createReadStream('in.png')
.pipe(new PNG({
filterType: 4
}))
.on('parsed', function() {
// and reduce opacity
this.data[idx + 3] = this.data[idx + 3] >> 1;
}
}
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
this.pack().pipe(fs.createWriteStream("out.png"));
});
// invert color
this.data[idx] = 255 - this.data[idx];
this.data[idx+1] = 255 - this.data[idx+1];
this.data[idx+2] = 255 - this.data[idx+2];
// and reduce opacity
this.data[idx+3] = this.data[idx+3] >> 1;
}
}
this.pack().pipe(fs.createWriteStream('out.png'));
});

View file

@ -1,25 +0,0 @@
let PNG = require("../lib/png").PNG;
let fs = require("fs");
let newfile = new PNG({ width: 10, height: 10 });
for (let y = 0; y < newfile.height; y++) {
for (let x = 0; x < newfile.width; x++) {
let idx = (newfile.width * y + x) << 2;
let col =
(x < newfile.width >> 1) ^ (y < newfile.height >> 1) ? 0xe5 : 0xff;
newfile.data[idx] = col;
newfile.data[idx + 1] = col;
newfile.data[idx + 2] = col;
newfile.data[idx + 3] = 0xff;
}
}
newfile
.pack()
.pipe(fs.createWriteStream(__dirname + "/newfile.png"))
.on("finish", function () {
console.log("Written!");
});

View file

@ -1,34 +0,0 @@
let fs = require("fs");
let PNG = require("../lib/png").PNG;
let w = 320;
let h = 200;
let bitmapWithoutAlpha = Buffer.alloc(w * h * 3);
let ofs = 0;
for (let i = 0; i < bitmapWithoutAlpha.length; i += 3) {
bitmapWithoutAlpha[ofs++] = 0xff;
bitmapWithoutAlpha[ofs++] = i % 0xff;
bitmapWithoutAlpha[ofs++] = (i / 3) % 0xff;
}
let png = new PNG({
width: w,
height: h,
bitDepth: 8,
colorType: 2,
inputHasAlpha: false,
});
png.data = bitmapWithoutAlpha;
png.pack().pipe(fs.createWriteStream("colortype2.png"));
png = new PNG({
width: w,
height: h,
bitDepth: 8,
colorType: 6,
inputHasAlpha: false,
});
png.data = bitmapWithoutAlpha;
png.pack().pipe(fs.createWriteStream("colortype6.png"));

View file

@ -1,28 +1,30 @@
#!/usr/bin/env node
let fs = require("fs"),
PNG = require("../lib/png").PNG;
var fs = require('fs'),
PNG = require('../lib/png').PNG;
let png = new PNG({
filterType: -1,
}),
src = fs.createReadStream(process.argv[2]),
dst = fs.createWriteStream(process.argv[3] || "out.png");
png.on("parsed", function () {
for (let y = 0; y < png.height; y++) {
for (let x = 0; x < png.width; x++) {
let idx = (png.width * y + x) << 2;
var png = new PNG({
filterType: -1
}),
src = fs.createReadStream(process.argv[2]),
dst = fs.createWriteStream(process.argv[3] || 'out.png');
if (
Math.abs(png.data[idx] - png.data[idx + 1]) <= 1 &&
Math.abs(png.data[idx + 1] - png.data[idx + 2]) <= 1
)
png.data[idx] = png.data[idx + 1] = png.data[idx + 2];
png.on('parsed', function() {
for (var y = 0; y < png.height; y++) {
for (var x = 0; x < png.width; x++) {
var idx = (png.width * y + x) << 2;
if (Math.abs(png.data[idx] - png.data[idx+1]) <= 1
&& Math.abs(png.data[idx+1] - png.data[idx+2]) <= 1)
png.data[idx] = png.data[idx+1] = png.data[idx+2];
}
}
}
png.pack().pipe(dst);
png.pack().pipe(dst);
});
src.pipe(png);

View file

@ -1,18 +0,0 @@
#!/usr/bin/env node
let fs = require("fs"),
PNG = require("../lib/png").PNG;
let srcFname = process.argv[2],
dstFname = process.argv[3] || "out.png";
// Read a PNG file
let data = fs.readFileSync(srcFname);
// Parse it
let png = PNG.sync.read(data, {
filterType: -1,
});
// Pack it back into a PNG data
let buff = PNG.sync.write(png);
// Write a PNG file
fs.writeFileSync(dstFname, buff);

27
examples/test/bg.js Normal file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env node
var fs = require('fs'),
PNG = require('pngjs').PNG;
var png = new PNG({
width: 10,
height: 10,
filterType: -1
});
for (var y = 0; y < png.height; y++) {
for (var x = 0; x < png.width; x++) {
var idx = (png.width * y + x) << 2;
var col = x < (png.width >> 1) ^ y < (png.height >> 1) ? 0xe5 : 0xff;
png.data[idx ] = col;
png.data[idx+1] = col;
png.data[idx+2] = col;
png.data[idx+3] = 0xff;
}
}
png.pack().pipe(fs.createWriteStream(__dirname + '/bg.png'));

BIN
examples/test/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

View file

@ -1,25 +1,25 @@
PNGSUITE
----------------
testset for PNG-(de)coders
created by Willem van Schaik
------------------------------------
This is a collection of graphics images created to test the png applications
like viewers, converters and editors. All (as far as that is possible)
formats supported by the PNG standard are represented.
The suite consists of the following files:
- PngSuite.README - this file
- PngSuite.LICENSE - the PngSuite is freeware
- PngSuite.png - image with PngSuite logo
- PngSuite.tgz - archive of all PNG testfiles
- PngSuite.zip - same in .zip format for PCs
--------
(c) Willem van Schaik
willem@schaik.com
Calgary, April 2011
PNGSUITE
----------------
testset for PNG-(de)coders
created by Willem van Schaik
------------------------------------
This is a collection of graphics images created to test the png applications
like viewers, converters and editors. All (as far as that is possible)
formats supported by the PNG standard are represented.
The suite consists of the following files:
- PngSuite.README - this file
- PngSuite.LICENSE - the PngSuite is freeware
- PngSuite.png - image with PngSuite logo
- PngSuite.tgz - archive of all PNG testfiles
- PngSuite.zip - same in .zip format for PCs
--------
(c) Willem van Schaik
willem@schaik.com
Calgary, April 2011

View file

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 138 B

View file

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 145 B

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 126 B

After

Width:  |  Height:  |  Size: 126 B

View file

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 184 B

View file

Before

Width:  |  Height:  |  Size: 319 B

After

Width:  |  Height:  |  Size: 319 B

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 321 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 355 B

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 389 B

After

Width:  |  Height:  |  Size: 389 B

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 269 B

View file

Before

Width:  |  Height:  |  Size: 985 B

After

Width:  |  Height:  |  Size: 985 B

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 719 B

After

Width:  |  Height:  |  Size: 719 B

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

38
examples/test/list.html Executable file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>PNG Test</title>
<style>
body { background: #eee; font: 12px Arial; margin: 10px; }
img { margin: 2px; background: url(bg.png); }
h3 { margin: 10px 0; }
</style>
</head>
<body>
<h3>Basic</h3>
<img src="img/basn0g08.png"> <img src="out/basn0g08.png"> grayscale<br>
<img src="img/basn2c08.png"> <img src="out/basn2c08.png"> color<br>
<img src="img/basn3p08.png"> <img src="out/basn3p08.png"> paletted<br>
<img src="img/basn4a08.png"> <img src="out/basn4a08.png"> grayscale + alpha<br>
<img src="img/basn6a08.png"> <img src="out/basn6a08.png"> color + alpha<br>
<h3>Image filtering</h3>
<img src="img/f00n0g08.png"> <img src="out/f00n0g08.png"> grayscale, filter 0<br>
<img src="img/f00n2c08.png"> <img src="out/f00n2c08.png"> color, filter 0<br>
<img src="img/f01n0g08.png"> <img src="out/f01n0g08.png"> grayscale, filter 1<br>
<img src="img/f01n2c08.png"> <img src="out/f01n2c08.png"> color, filter 1<br>
<img src="img/f02n0g08.png"> <img src="out/f02n0g08.png"> grayscale, filter 2<br>
<img src="img/f02n2c08.png"> <img src="out/f02n2c08.png"> color, filter 2<br>
<img src="img/f03n0g08.png"> <img src="out/f03n0g08.png"> grayscale, filter 3<br>
<img src="img/f03n2c08.png"> <img src="out/f03n2c08.png"> color, filter 3<br>
<img src="img/f04n0g08.png"> <img src="out/f04n0g08.png"> grayscale, filter 4<br>
<img src="img/f04n2c08.png"> <img src="out/f04n2c08.png"> color, filter 4<br>
<h3>Transparency</h3>
<img src="img/tp0n0g08.png"> <img src="out/tp0n0g08.png"> grayscale, not transparent<br>
<img src="img/tp0n2c08.png"> <img src="out/tp0n2c08.png"> color, not transparent<br>
<img src="img/tp0n3p08.png"> <img src="out/tp0n3p08.png"> paletted, not transparent<br>
<img src="img/tbrn2c08.png"> <img src="out/tbrn2c08.png"> color, transparent<br>
<img src="img/tp1n3p08.png"> <img src="out/tp1n3p08.png"> paletted, transparent<br>
</body>
</html>

38
examples/test/test.js Executable file
View file

@ -0,0 +1,38 @@
var fs = require('fs'),
PNG = require('pngjs').PNG;
fs.readdir(__dirname + '/img/', function(err, files) {
if (err) throw err;
files.forEach(function(file) {
if (!file.match(/\.png$/i))
return;
fs.createReadStream(__dirname + '/img/' + file)
.pipe(new PNG())
.on('parsed', function() {
if (this.gamma) {
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
var idx = (this.width * y + x) << 2;
for (var i = 0; i < 3; i++) {
var sample = this.data[idx + i] / 255;
sample = Math.pow(sample, 1 / 2.2 / this.gamma);
this.data[idx + i] = Math.round(sample * 255);
}
}
}
}
this.pack()
.pipe(fs.createWriteStream(__dirname + '/out/' + file));
});
});
});

View file

@ -1,265 +0,0 @@
import interlaceUtils from "./interlace.js";
let pixelBppMapper = [
// 0 - dummy entry
function () {},
// 1 - L
// 0: 0, 1: 0, 2: 0, 3: 0xff
function (pxData, data, pxPos, rawPos) {
if (rawPos === data.length) {
throw new Error("Ran out of data");
}
let pixel = data[rawPos];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = 0xff;
},
// 2 - LA
// 0: 0, 1: 0, 2: 0, 3: 1
function (pxData, data, pxPos, rawPos) {
if (rawPos + 1 >= data.length) {
throw new Error("Ran out of data");
}
let pixel = data[rawPos];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = data[rawPos + 1];
},
// 3 - RGB
// 0: 0, 1: 1, 2: 2, 3: 0xff
function (pxData, data, pxPos, rawPos) {
if (rawPos + 2 >= data.length) {
throw new Error("Ran out of data");
}
pxData[pxPos] = data[rawPos];
pxData[pxPos + 1] = data[rawPos + 1];
pxData[pxPos + 2] = data[rawPos + 2];
pxData[pxPos + 3] = 0xff;
},
// 4 - RGBA
// 0: 0, 1: 1, 2: 2, 3: 3
function (pxData, data, pxPos, rawPos) {
if (rawPos + 3 >= data.length) {
throw new Error("Ran out of data");
}
pxData[pxPos] = data[rawPos];
pxData[pxPos + 1] = data[rawPos + 1];
pxData[pxPos + 2] = data[rawPos + 2];
pxData[pxPos + 3] = data[rawPos + 3];
},
];
let pixelBppCustomMapper = [
// 0 - dummy entry
function () {},
// 1 - L
// 0: 0, 1: 0, 2: 0, 3: 0xff
function (pxData, pixelData, pxPos, maxBit) {
let pixel = pixelData[0];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = maxBit;
},
// 2 - LA
// 0: 0, 1: 0, 2: 0, 3: 1
function (pxData, pixelData, pxPos) {
let pixel = pixelData[0];
pxData[pxPos] = pixel;
pxData[pxPos + 1] = pixel;
pxData[pxPos + 2] = pixel;
pxData[pxPos + 3] = pixelData[1];
},
// 3 - RGB
// 0: 0, 1: 1, 2: 2, 3: 0xff
function (pxData, pixelData, pxPos, maxBit) {
pxData[pxPos] = pixelData[0];
pxData[pxPos + 1] = pixelData[1];
pxData[pxPos + 2] = pixelData[2];
pxData[pxPos + 3] = maxBit;
},
// 4 - RGBA
// 0: 0, 1: 1, 2: 2, 3: 3
function (pxData, pixelData, pxPos) {
pxData[pxPos] = pixelData[0];
pxData[pxPos + 1] = pixelData[1];
pxData[pxPos + 2] = pixelData[2];
pxData[pxPos + 3] = pixelData[3];
},
];
function bitRetriever(data, depth) {
let leftOver = [];
let i = 0;
function split() {
if (i === data.length) {
throw new Error("Ran out of data");
}
let byte = data[i];
i++;
let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1;
switch (depth) {
default:
throw new Error("unrecognised depth");
case 16:
byte2 = data[i];
i++;
leftOver.push((byte << 8) + byte2);
break;
case 4:
byte2 = byte & 0x0f;
byte1 = byte >> 4;
leftOver.push(byte1, byte2);
break;
case 2:
byte4 = byte & 3;
byte3 = (byte >> 2) & 3;
byte2 = (byte >> 4) & 3;
byte1 = (byte >> 6) & 3;
leftOver.push(byte1, byte2, byte3, byte4);
break;
case 1:
byte8 = byte & 1;
byte7 = (byte >> 1) & 1;
byte6 = (byte >> 2) & 1;
byte5 = (byte >> 3) & 1;
byte4 = (byte >> 4) & 1;
byte3 = (byte >> 5) & 1;
byte2 = (byte >> 6) & 1;
byte1 = (byte >> 7) & 1;
leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8);
break;
}
}
return {
get: function (count) {
while (leftOver.length < count) {
split();
}
let returner = leftOver.slice(0, count);
leftOver = leftOver.slice(count);
return returner;
},
resetAfterLine: function () {
leftOver.length = 0;
},
end: function () {
if (i !== data.length) {
throw new Error("extra data found");
}
},
};
}
function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) {
// eslint-disable-line max-params
let imageWidth = image.width;
let imageHeight = image.height;
let imagePass = image.index;
for (let y = 0; y < imageHeight; y++) {
for (let x = 0; x < imageWidth; x++) {
let pxPos = getPxPos(x, y, imagePass);
pixelBppMapper[bpp](pxData, data, pxPos, rawPos);
rawPos += bpp; //eslint-disable-line no-param-reassign
}
}
return rawPos;
}
function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) {
// eslint-disable-line max-params
let imageWidth = image.width;
let imageHeight = image.height;
let imagePass = image.index;
for (let y = 0; y < imageHeight; y++) {
for (let x = 0; x < imageWidth; x++) {
let pixelData = bits.get(bpp);
let pxPos = getPxPos(x, y, imagePass);
pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit);
}
bits.resetAfterLine();
}
}
export default function (data, bitmapInfo) {
let width = bitmapInfo.width;
let height = bitmapInfo.height;
let depth = bitmapInfo.depth;
let bpp = bitmapInfo.bpp;
let interlace = bitmapInfo.interlace;
let bits;
if (depth !== 8) {
bits = bitRetriever(data, depth);
}
let pxData;
if (depth <= 8) {
pxData = Buffer.alloc(width * height * 4);
} else {
pxData = new Uint16Array(width * height * 4);
}
let maxBit = Math.pow(2, depth) - 1;
let rawPos = 0;
let images;
let getPxPos;
if (interlace) {
images = interlaceUtils.getImagePasses(width, height);
getPxPos = interlaceUtils.getInterlaceIterator(width, height);
} else {
let nonInterlacedPxPos = 0;
getPxPos = function () {
let returner = nonInterlacedPxPos;
nonInterlacedPxPos += 4;
return returner;
};
images = [{ width: width, height: height }];
}
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
if (depth === 8) {
rawPos = mapImage8Bit(
images[imageIndex],
pxData,
getPxPos,
bpp,
data,
rawPos
);
} else {
mapImageCustomBit(
images[imageIndex],
pxData,
getPxPos,
bpp,
bits,
maxBit
);
}
}
if (depth === 8) {
if (rawPos !== data.length) {
throw new Error("extra data found");
}
} else {
bits.end();
}
return pxData;
};

View file

@ -1,156 +0,0 @@
import constants from "./constants.js";
export default function (dataIn, width, height, options) {
let outHasAlpha =
[constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf(
options.colorType
) !== -1;
if (options.colorType === options.inputColorType) {
let bigEndian = (function () {
let 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.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) {
return dataIn;
}
}
// map to a UInt16 array if data is 16bit, fix endianness below
let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
let maxValue = 255;
let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType];
if (inBpp === 4 && !options.inputHasAlpha) {
inBpp = 3;
}
let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType];
if (options.bitDepth === 16) {
maxValue = 65535;
outBpp *= 2;
}
let outData = Buffer.alloc(width * height * outBpp);
let inIndex = 0;
let outIndex = 0;
let bgColor = options.bgColor || {};
if (bgColor.red === undefined) {
bgColor.red = maxValue;
}
if (bgColor.green === undefined) {
bgColor.green = maxValue;
}
if (bgColor.blue === undefined) {
bgColor.blue = maxValue;
}
function getRGBA() {
let red;
let green;
let blue;
let 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: red, green: green, blue: blue, alpha: alpha };
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let rgba = getRGBA(data, inIndex);
switch (options.colorType) {
case constants.COLORTYPE_COLOR_ALPHA:
case constants.COLORTYPE_COLOR:
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
let 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;
}
default:
throw new Error("unrecognised color Type " + options.colorType);
}
inIndex += inBpp;
outIndex += outBpp;
}
}
return outData;
};

316
lib/chunkstream.js Normal file → Executable file
View file

@ -1,190 +1,200 @@
let util = require("util");
let Stream = require("stream");
// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
let ChunkStream = function () {
Stream.call(this);
'use strict';
this._buffers = [];
this._buffered = 0;
this._reads = [];
this._paused = false;
var util = require('util'),
Stream = require('stream');
this._encoding = "utf8";
this.writable = true;
var ChunkStream = module.exports = function() {
Stream.call(this);
this._buffers = [];
this._buffered = 0;
this._reads = [];
this._paused = false;
this._encoding = 'utf8';
this.writable = true;
};
export default ChunkStream;
util.inherits(ChunkStream, Stream);
ChunkStream.prototype.read = function (length, callback) {
this._reads.push({
length: Math.abs(length), // if length < 0 then at most this length
allowLess: length < 0,
func: callback,
});
process.nextTick(
function () {
ChunkStream.prototype.read = function(length, callback) {
this._reads.push({
length: Math.abs(length), // if length < 0 then at most this length
allowLess: length < 0,
func: callback
});
process.nextTick(function() {
this._process();
// its paused and there is not enought data then ask for more
if (this._paused && this._reads && this._reads.length > 0) {
this._paused = false;
if (this._paused && this._reads.length > 0) {
this._paused = false;
this.emit("drain");
this.emit('drain');
}
}.bind(this)
);
}.bind(this));
};
ChunkStream.prototype.write = function (data, encoding) {
if (!this.writable) {
this.emit("error", new Error("Stream not writable"));
return false;
}
ChunkStream.prototype.write = function(data, encoding) {
let dataBuffer;
if (Buffer.isBuffer(data)) {
dataBuffer = data;
} else {
dataBuffer = Buffer.from(data, encoding || this._encoding);
}
if (!this.writable) {
this.emit('error', new Error('Stream not writable'));
return false;
}
this._buffers.push(dataBuffer);
this._buffered += dataBuffer.length;
if (!Buffer.isBuffer(data))
data = new Buffer(data, encoding || this._encoding);
this._process();
this._buffers.push(data);
this._buffered += data.length;
// ok if there are no more read requests
if (this._reads && this._reads.length === 0) {
this._paused = true;
}
return this.writable && !this._paused;
};
ChunkStream.prototype.end = function (data, encoding) {
if (data) {
this.write(data, encoding);
}
this.writable = false;
// already destroyed
if (!this._buffers) {
return;
}
// enqueue or handle end
if (this._buffers.length === 0) {
this._end();
} else {
this._buffers.push(null);
this._process();
}
// ok if there are no more read requests
if (this._reads && this._reads.length == 0)
this._paused = true;
return this.writable && !this._paused;
};
ChunkStream.prototype.end = function(data, encoding) {
if (data) this.write(data, encoding);
this.writable = false;
// already destroyed
if (!this._buffers) return;
// enqueue or handle end
if (this._buffers.length == 0) {
this._end();
} else {
this._buffers.push(null);
this._process();
}
};
ChunkStream.prototype.destroySoon = ChunkStream.prototype.end;
ChunkStream.prototype._end = function () {
if (this._reads.length > 0) {
this.emit("error", new Error("Unexpected end of input"));
}
ChunkStream.prototype._end = function() {
this.destroy();
};
ChunkStream.prototype.destroy = function () {
if (!this._buffers) {
return;
}
this.writable = false;
this._reads = null;
this._buffers = null;
this.emit("close");
};
ChunkStream.prototype._processReadAllowingLess = function (read) {
// ok there is any data so that we can satisfy this request
this._reads.shift(); // == read
// first we need to peek into first buffer
let smallerBuf = this._buffers[0];
// ok there is more data than we need
if (smallerBuf.length > read.length) {
this._buffered -= read.length;
this._buffers[0] = smallerBuf.slice(read.length);
read.func.call(this, smallerBuf.slice(0, read.length));
} else {
// ok this is less than maximum length so use it all
this._buffered -= smallerBuf.length;
this._buffers.shift(); // == smallerBuf
read.func.call(this, smallerBuf);
}
};
ChunkStream.prototype._processRead = function (read) {
this._reads.shift(); // == read
let pos = 0;
let count = 0;
let data = Buffer.alloc(read.length);
// create buffer for all data
while (pos < read.length) {
let buf = this._buffers[count++];
let len = Math.min(buf.length, read.length - pos);
buf.copy(data, pos, 0, len);
pos += len;
// last buffer wasn't used all so just slice it and leave
if (len !== buf.length) {
this._buffers[--count] = buf.slice(len);
if (this._reads.length > 0) {
this.emit('error',
new Error('There are some read requests waitng on finished stream')
);
}
}
// remove all used buffers
if (count > 0) {
this._buffers.splice(0, count);
}
this._buffered -= read.length;
read.func.call(this, data);
this.destroy();
};
ChunkStream.prototype._process = function () {
try {
ChunkStream.prototype.destroy = function() {
if (!this._buffers) return;
this.writable = false;
this._reads = null;
this._buffers = null;
this.emit('close');
};
ChunkStream.prototype._process = function() {
// as long as there is any data and read requests
while (this._buffered > 0 && this._reads && this._reads.length > 0) {
let read = this._reads[0];
// read any data (but no more than length)
if (read.allowLess) {
this._processReadAllowingLess(read);
} else if (this._buffered >= read.length) {
// ok we can meet some expectations
var read = this._reads[0];
this._processRead(read);
} else {
// not enought data to satisfy first request in queue
// so we need to wait for more
break;
}
// read any data (but no more than length)
if (read.allowLess) {
// ok there is any data so that we can satisfy this request
this._reads.shift(); // == read
// first we need to peek into first buffer
var buf = this._buffers[0];
// ok there is more data than we need
if (buf.length > read.length) {
this._buffered -= read.length;
this._buffers[0] = buf.slice(read.length);
read.func.call(this, buf.slice(0, read.length));
} else {
// ok this is less than maximum length so use it all
this._buffered -= buf.length;
this._buffers.shift(); // == buf
read.func.call(this, buf);
}
} else if (this._buffered >= read.length) {
// ok we can meet some expectations
this._reads.shift(); // == read
var pos = 0,
count = 0,
data = new Buffer(read.length);
// create buffer for all data
while (pos < read.length) {
var buf = this._buffers[count++],
len = Math.min(buf.length, read.length - pos);
buf.copy(data, pos, 0, len);
pos += len;
// last buffer wasn't used all so just slice it and leave
if (len != buf.length)
this._buffers[--count] = buf.slice(len);
}
// remove all used buffers
if (count > 0)
this._buffers.splice(0, count);
this._buffered -= read.length;
read.func.call(this, data);
} else {
// not enought data to satisfy first request in queue
// so we need to wait for more
break;
}
}
if (this._buffers && !this.writable) {
this._end();
if (this._buffers && this._buffers.length > 0 && this._buffers[0] == null) {
this._end();
}
} catch (ex) {
this.emit("error", ex);
}
};

56
lib/constants.js Normal file → Executable file
View file

@ -1,30 +1,38 @@
export default {
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
TYPE_IHDR: 0x49484452,
TYPE_IEND: 0x49454e44,
TYPE_IDAT: 0x49444154,
TYPE_PLTE: 0x504c5445,
TYPE_tRNS: 0x74524e53, // eslint-disable-line camelcase
TYPE_gAMA: 0x67414d41, // eslint-disable-line camelcase
'use strict';
// color-type bits
COLORTYPE_GRAYSCALE: 0,
COLORTYPE_PALETTE: 1,
COLORTYPE_COLOR: 2,
COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha
// color-type combinations
COLORTYPE_PALETTE_COLOR: 3,
COLORTYPE_COLOR_ALPHA: 6,
module.exports = {
COLORTYPE_TO_BPP_MAP: {
0: 1,
2: 3,
3: 1,
4: 2,
6: 4,
},
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
GAMMA_DIVISION: 100000,
TYPE_IHDR: 0x49484452,
TYPE_IEND: 0x49454e44,
TYPE_IDAT: 0x49444154,
TYPE_PLTE: 0x504c5445,
TYPE_tRNS: 0x74524e53,
TYPE_gAMA: 0x67414d41,
COLOR_PALETTE: 1,
COLOR_COLOR: 2,
COLOR_ALPHA: 4
};

99
lib/crc.js Normal file → Executable file
View file

@ -1,40 +1,79 @@
let crcTable = [];
// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
(function () {
for (let i = 0; i < 256; i++) {
let currentCrc = i;
for (let j = 0; j < 8; j++) {
if (currentCrc & 1) {
currentCrc = 0xedb88320 ^ (currentCrc >>> 1);
} else {
currentCrc = currentCrc >>> 1;
}
'use strict';
var util = require('util'),
Stream = require('stream');
var CrcStream = module.exports = function() {
Stream.call(this);
this._crc = -1;
this.writable = true;
};
util.inherits(CrcStream, Stream);
CrcStream.prototype.write = function(data) {
for (var i = 0; i < data.length; i++) {
this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8);
}
crcTable[i] = currentCrc;
}
})();
let CrcCalculator = function () {
this._crc = -1;
return true;
};
export default CrcCalculator;
CrcStream.prototype.end = function(data) {
if (data) this.write(data);
CrcCalculator.prototype.write = function (data) {
for (let i = 0; i < data.length; i++) {
this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8);
}
return true;
this.emit('crc', this.crc32());
};
CrcCalculator.prototype.crc32 = function () {
return this._crc ^ -1;
CrcStream.prototype.crc32 = function() {
return this._crc ^ -1;
};
CrcCalculator.crc32 = function (buf) {
let crc = -1;
for (let i = 0; i < buf.length; i++) {
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
}
return crc ^ -1;
CrcStream.crc32 = function(buf) {
var crc = -1;
for (var i = 0; i < buf.length; i++) {
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
}
return crc ^ -1;
};
var crcTable = [];
for (var i = 0; i < 256; i++) {
var c = i;
for (var j = 0; j < 8; j++) {
if (c & 1) {
c = 0xedb88320 ^ (c >>> 1);
} else {
c = c >>> 1;
}
}
crcTable[i] = c;
}

View file

@ -1,169 +0,0 @@
import paethPredictor from "./paeth-predictor.js";
function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) {
for (let x = 0; x < byteWidth; x++) {
rawData[rawPos + x] = pxData[pxPos + x];
}
}
function filterSumNone(pxData, pxPos, byteWidth) {
let sum = 0;
let length = pxPos + byteWidth;
for (let i = pxPos; i < length; i++) {
sum += Math.abs(pxData[i]);
}
return sum;
}
function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let val = pxData[pxPos + x] - left;
rawData[rawPos + x] = val;
}
}
function filterSumSub(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let val = pxData[pxPos + x] - left;
sum += Math.abs(val);
}
return sum;
}
function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) {
for (let x = 0; x < byteWidth; x++) {
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - up;
rawData[rawPos + x] = val;
}
}
function filterSumUp(pxData, pxPos, byteWidth) {
let sum = 0;
let length = pxPos + byteWidth;
for (let x = pxPos; x < length; x++) {
let up = pxPos > 0 ? pxData[x - byteWidth] : 0;
let val = pxData[x] - up;
sum += Math.abs(val);
}
return sum;
}
function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - ((left + up) >> 1);
rawData[rawPos + x] = val;
}
}
function filterSumAvg(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let val = pxData[pxPos + x] - ((left + up) >> 1);
sum += Math.abs(val);
}
return sum;
}
function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let upleft =
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
rawData[rawPos + x] = val;
}
}
function filterSumPaeth(pxData, pxPos, byteWidth, bpp) {
let sum = 0;
for (let x = 0; x < byteWidth; x++) {
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
let upleft =
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
sum += Math.abs(val);
}
return sum;
}
let filters = {
0: filterNone,
1: filterSub,
2: filterUp,
3: filterAvg,
4: filterPaeth,
};
let filterSums = {
0: filterSumNone,
1: filterSumSub,
2: filterSumUp,
3: filterSumAvg,
4: filterSumPaeth,
};
export default function (pxData, width, height, options, bpp) {
let filterTypes;
if (!("filterType" in options) || options.filterType === -1) {
filterTypes = [0, 1, 2, 3, 4];
} else if (typeof options.filterType === "number") {
filterTypes = [options.filterType];
} else {
throw new Error("unrecognised filter types");
}
if (options.bitDepth === 16) {
bpp *= 2;
}
let byteWidth = width * bpp;
let rawPos = 0;
let pxPos = 0;
let rawData = Buffer.alloc((byteWidth + 1) * height);
let sel = filterTypes[0];
for (let y = 0; y < height; y++) {
if (filterTypes.length > 1) {
// find best filter for this line (with lowest sum of values)
let min = Infinity;
for (let i = 0; i < filterTypes.length; i++) {
let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp);
if (sum < min) {
sel = filterTypes[i];
min = sum;
}
}
}
rawData[rawPos] = sel;
rawPos++;
filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp);
rawPos += byteWidth;
pxPos += byteWidth;
}
return rawData;
};

View file

@ -1,24 +0,0 @@
import ChunkStream from "./chunkstream.js";
import Filter from "./filter-parse.js";
let util = require("util");
let FilterAsync = function (bitmapInfo) {
ChunkStream.call(this);
let buffers = [];
let that = this;
this._filter = new Filter(bitmapInfo, {
read: this.read.bind(this),
write: function (buffer) {
buffers.push(buffer);
},
complete: function () {
that.emit("complete", Buffer.concat(buffers));
},
});
this._filter.start();
};
util.inherits(FilterAsync, ChunkStream);
export default FilterAsync;

View file

@ -1,19 +0,0 @@
import SyncReader from "./sync-reader.js";
import Filter from "./filter-parse.js";
export default function (inBuffer, bitmapInfo) {
let outBuffers = [];
let reader = new SyncReader(inBuffer);
let filter = new Filter(bitmapInfo, {
read: reader.read.bind(reader),
write: function (bufferPart) {
outBuffers.push(bufferPart);
},
complete: function () {},
});
filter.start();
reader.process();
return Buffer.concat(outBuffers);
};

View file

@ -1,177 +0,0 @@
import interlaceUtils from "./interlace.js";
import paethPredictor from "./paeth-predictor.js";
function getByteWidth(width, bpp, depth) {
let byteWidth = width * bpp;
if (depth !== 8) {
byteWidth = Math.ceil(byteWidth / (8 / depth));
}
return byteWidth;
}
let Filter = function (bitmapInfo, dependencies) {
let width = bitmapInfo.width;
let height = bitmapInfo.height;
let interlace = bitmapInfo.interlace;
let bpp = bitmapInfo.bpp;
let depth = bitmapInfo.depth;
this.read = dependencies.read;
this.write = dependencies.write;
this.complete = dependencies.complete;
this._imageIndex = 0;
this._images = [];
if (interlace) {
let passes = interlaceUtils.getImagePasses(width, height);
for (let i = 0; i < passes.length; i++) {
this._images.push({
byteWidth: getByteWidth(passes[i].width, bpp, depth),
height: passes[i].height,
lineIndex: 0,
});
}
} else {
this._images.push({
byteWidth: getByteWidth(width, bpp, depth),
height: height,
lineIndex: 0,
});
}
// when filtering the line we look at the pixel to the left
// the spec also says it is done on a byte level regardless of the number of pixels
// so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back
// a pixel rather than just a different byte part. However if we are sub byte, we ignore.
if (depth === 8) {
this._xComparison = bpp;
} else if (depth === 16) {
this._xComparison = bpp * 2;
} else {
this._xComparison = 1;
}
};
export default Filter;
Filter.prototype.start = function () {
this.read(
this._images[this._imageIndex].byteWidth + 1,
this._reverseFilterLine.bind(this)
);
};
Filter.prototype._unFilterType1 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
unfilteredLine[x] = rawByte + f1Left;
}
};
Filter.prototype._unFilterType2 = function (
rawData,
unfilteredLine,
byteWidth
) {
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f2Up = lastLine ? lastLine[x] : 0;
unfilteredLine[x] = rawByte + f2Up;
}
};
Filter.prototype._unFilterType3 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f3Up = lastLine ? lastLine[x] : 0;
let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
let f3Add = Math.floor((f3Left + f3Up) / 2);
unfilteredLine[x] = rawByte + f3Add;
}
};
Filter.prototype._unFilterType4 = function (
rawData,
unfilteredLine,
byteWidth
) {
let xComparison = this._xComparison;
let xBiggerThan = xComparison - 1;
let lastLine = this._lastLine;
for (let x = 0; x < byteWidth; x++) {
let rawByte = rawData[1 + x];
let f4Up = lastLine ? lastLine[x] : 0;
let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0;
let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft);
unfilteredLine[x] = rawByte + f4Add;
}
};
Filter.prototype._reverseFilterLine = function (rawData) {
let filter = rawData[0];
let unfilteredLine;
let currentImage = this._images[this._imageIndex];
let byteWidth = currentImage.byteWidth;
if (filter === 0) {
unfilteredLine = rawData.slice(1, byteWidth + 1);
} else {
unfilteredLine = Buffer.alloc(byteWidth);
switch (filter) {
case 1:
this._unFilterType1(rawData, unfilteredLine, byteWidth);
break;
case 2:
this._unFilterType2(rawData, unfilteredLine, byteWidth);
break;
case 3:
this._unFilterType3(rawData, unfilteredLine, byteWidth);
break;
case 4:
this._unFilterType4(rawData, unfilteredLine, byteWidth);
break;
default:
throw new Error("Unrecognised filter type - " + filter);
}
}
this.write(unfilteredLine);
currentImage.lineIndex++;
if (currentImage.lineIndex >= currentImage.height) {
this._lastLine = null;
this._imageIndex++;
currentImage = this._images[this._imageIndex];
} else {
this._lastLine = unfilteredLine;
}
if (currentImage) {
// read, using the byte width that may be from the new current image
this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this));
} else {
this._lastLine = null;
this.complete();
}
};

314
lib/filter.js Executable file
View file

@ -0,0 +1,314 @@
// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
'use strict';
var util = require('util'),
zlib = require('zlib'),
ChunkStream = require('./chunkstream');
var Filter = module.exports = function(width, height, Bpp, data, options) {
ChunkStream.call(this);
this._width = width;
this._height = height;
this._Bpp = Bpp;
this._data = data;
this._options = options;
this._line = 0;
if (!('filterType' in options) || options.filterType == -1) {
options.filterType = [0, 1, 2, 3, 4];
} else if (typeof options.filterType == 'number') {
options.filterType = [options.filterType];
}
this._filters = {
0: this._filterNone.bind(this),
1: this._filterSub.bind(this),
2: this._filterUp.bind(this),
3: this._filterAvg.bind(this),
4: this._filterPaeth.bind(this)
};
this.read(this._width * Bpp + 1, this._reverseFilterLine.bind(this));
};
util.inherits(Filter, ChunkStream);
var pixelBppMap = {
1: { // L
0: 0,
1: 0,
2: 0,
3: 0xff
},
2: { // LA
0: 0,
1: 0,
2: 0,
3: 1
},
3: { // RGB
0: 0,
1: 1,
2: 2,
3: 0xff
},
4: { // RGBA
0: 0,
1: 1,
2: 2,
3: 3
}
};
Filter.prototype._reverseFilterLine = function(rawData) {
var pxData = this._data,
pxLineLength = this._width << 2,
pxRowPos = this._line * pxLineLength,
filter = rawData[0];
if (filter == 0) {
for (var x = 0; x < this._width; x++) {
var pxPos = pxRowPos + (x << 2),
rawPos = 1 + x * this._Bpp;
for (var i = 0; i < 4; i++) {
var idx = pixelBppMap[this._Bpp][i];
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] : 0xff;
}
}
} else if (filter == 1) {
for (var x = 0; x < this._width; x++) {
var pxPos = pxRowPos + (x << 2),
rawPos = 1 + x * this._Bpp;
for (var i = 0; i < 4; i++) {
var idx = pixelBppMap[this._Bpp][i],
left = x > 0 ? pxData[pxPos + i - 4] : 0;
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + left : 0xff;
}
}
} else if (filter == 2) {
for (var x = 0; x < this._width; x++) {
var pxPos = pxRowPos + (x << 2),
rawPos = 1 + x * this._Bpp;
for (var i = 0; i < 4; i++) {
var idx = pixelBppMap[this._Bpp][i],
up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0;
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + up : 0xff;
}
}
} else if (filter == 3) {
for (var x = 0; x < this._width; x++) {
var pxPos = pxRowPos + (x << 2),
rawPos = 1 + x * this._Bpp;
for (var i = 0; i < 4; i++) {
var idx = pixelBppMap[this._Bpp][i],
left = x > 0 ? pxData[pxPos + i - 4] : 0,
up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0,
add = Math.floor((left + up) / 2);
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + add : 0xff;
}
}
} else if (filter == 4) {
for (var x = 0; x < this._width; x++) {
var pxPos = pxRowPos + (x << 2),
rawPos = 1 + x * this._Bpp;
for (var i = 0; i < 4; i++) {
var idx = pixelBppMap[this._Bpp][i],
left = x > 0 ? pxData[pxPos + i - 4] : 0,
up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0,
upLeft = x > 0 && this._line > 0
? pxData[pxPos - pxLineLength + i - 4] : 0,
add = PaethPredictor(left, up, upLeft);
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + add : 0xff;
}
}
}
this._line++;
if (this._line < this._height)
this.read(this._width * this._Bpp + 1, this._reverseFilterLine.bind(this));
else
this.emit('complete', this._data, this._width, this._height);
};
Filter.prototype.filter = function() {
var pxData = this._data,
rawData = new Buffer(((this._width << 2) + 1) * this._height);
for (var y = 0; y < this._height; y++) {
// find best filter for this line (with lowest sum of values)
var filterTypes = this._options.filterType,
min = Infinity,
sel = 0;
for (var i = 0; i < filterTypes.length; i++) {
var sum = this._filters[filterTypes[i]](pxData, y, null);
if (sum < min) {
sel = filterTypes[i];
min = sum;
}
}
this._filters[sel](pxData, y, rawData);
}
return rawData;
};
Filter.prototype._filterNone = function(pxData, y, rawData) {
var pxRowLength = this._width << 2,
rawRowLength = pxRowLength + 1,
sum = 0;
if (!rawData) {
for (var x = 0; x < pxRowLength; x++)
sum += Math.abs(pxData[y * pxRowLength + x]);
} else {
rawData[y * rawRowLength] = 0;
pxData.copy(rawData, rawRowLength * y + 1, pxRowLength * y, pxRowLength * (y + 1));
}
return sum;
};
Filter.prototype._filterSub = function(pxData, y, rawData) {
var pxRowLength = this._width << 2,
rawRowLength = pxRowLength + 1,
sum = 0;
if (rawData)
rawData[y * rawRowLength] = 1;
for (var x = 0; x < pxRowLength; x++) {
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
val = pxData[y * pxRowLength + x] - left;
if (!rawData) sum += Math.abs(val);
else rawData[y * rawRowLength + 1 + x] = val;
}
return sum;
};
Filter.prototype._filterUp = function(pxData, y, rawData) {
var pxRowLength = this._width << 2,
rawRowLength = pxRowLength + 1,
sum = 0;
if (rawData)
rawData[y * rawRowLength] = 2;
for (var x = 0; x < pxRowLength; x++) {
var up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
val = pxData[y * pxRowLength + x] - up;
if (!rawData) sum += Math.abs(val);
else rawData[y * rawRowLength + 1 + x] = val;
}
return sum;
};
Filter.prototype._filterAvg = function(pxData, y, rawData) {
var pxRowLength = this._width << 2,
rawRowLength = pxRowLength + 1,
sum = 0;
if (rawData)
rawData[y * rawRowLength] = 3;
for (var x = 0; x < pxRowLength; x++) {
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
val = pxData[y * pxRowLength + x] - ((left + up) >> 1);
if (!rawData) sum += Math.abs(val);
else rawData[y * rawRowLength + 1 + x] = val;
}
return sum;
};
Filter.prototype._filterPaeth = function(pxData, y, rawData) {
var pxRowLength = this._width << 2,
rawRowLength = pxRowLength + 1,
sum = 0;
if (rawData)
rawData[y * rawRowLength] = 4;
for (var x = 0; x < pxRowLength; x++) {
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
upLeft = x >= 4 && y > 0 ? pxData[(y - 1) * pxRowLength + x - 4] : 0,
val = pxData[y * pxRowLength + x] - PaethPredictor(left, up, upLeft);
if (!rawData) sum += Math.abs(val);
else rawData[y * rawRowLength + 1 + x] = val;
}
return sum;
};
var PaethPredictor = function(left, above, upLeft) {
var p = left + above - upLeft,
pLeft = Math.abs(p - left),
pAbove = Math.abs(p - above),
pUpLeft = Math.abs(p - upLeft);
if (pLeft <= pAbove && pLeft <= pUpLeft) return left;
else if (pAbove <= pUpLeft) return above;
else return upLeft;
};

View file

@ -1,91 +0,0 @@
function dePalette(indata, outdata, width, height, palette) {
let pxPos = 0;
// use values from palette
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let color = palette[indata[pxPos]];
if (!color) {
throw new Error("index " + indata[pxPos] + " not in palette");
}
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = color[i];
}
pxPos += 4;
}
}
}
function replaceTransparentColor(indata, outdata, width, height, transColor) {
let pxPos = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let makeTrans = false;
if (transColor.length === 1) {
if (transColor[0] === indata[pxPos]) {
makeTrans = true;
}
} else if (
transColor[0] === indata[pxPos] &&
transColor[1] === indata[pxPos + 1] &&
transColor[2] === indata[pxPos + 2]
) {
makeTrans = true;
}
if (makeTrans) {
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = 0;
}
}
pxPos += 4;
}
}
}
function scaleDepth(indata, outdata, width, height, depth) {
let maxOutSample = 255;
let maxInSample = Math.pow(2, depth) - 1;
let pxPos = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
for (let i = 0; i < 4; i++) {
outdata[pxPos + i] = Math.floor(
(indata[pxPos + i] * maxOutSample) / maxInSample + 0.5
);
}
pxPos += 4;
}
}
}
export default function (indata, imageData) {
let depth = imageData.depth;
let width = imageData.width;
let height = imageData.height;
let colorType = imageData.colorType;
let transColor = imageData.transColor;
let palette = imageData.palette;
let outdata = indata; // only different for 16 bits
if (colorType === 3) {
// paletted
dePalette(indata, outdata, width, height, palette);
} else {
if (transColor) {
replaceTransparentColor(indata, outdata, width, height, transColor);
}
// if it needs scaling
if (depth !== 8) {
// if we need to change the buffer size
if (depth === 16) {
outdata = Buffer.alloc(width * height * 4);
}
scaleDepth(indata, outdata, width, height, depth);
}
}
return outdata;
};

View file

@ -1,98 +0,0 @@
// Adam 7
// 0 1 2 3 4 5 6 7
// 0 x 6 4 6 x 6 4 6
// 1 7 7 7 7 7 7 7 7
// 2 5 6 5 6 5 6 5 6
// 3 7 7 7 7 7 7 7 7
// 4 3 6 4 6 3 6 4 6
// 5 7 7 7 7 7 7 7 7
// 6 5 6 5 6 5 6 5 6
// 7 7 7 7 7 7 7 7 7
let imagePasses = [
{
// pass 1 - 1px
x: [0],
y: [0],
},
{
// pass 2 - 1px
x: [4],
y: [0],
},
{
// pass 3 - 2px
x: [0, 4],
y: [4],
},
{
// pass 4 - 4px
x: [2, 6],
y: [0, 4],
},
{
// pass 5 - 8px
x: [0, 2, 4, 6],
y: [2, 6],
},
{
// pass 6 - 16px
x: [1, 3, 5, 7],
y: [0, 2, 4, 6],
},
{
// pass 7 - 32px
x: [0, 1, 2, 3, 4, 5, 6, 7],
y: [1, 3, 5, 7],
},
];
export var getImagePasses = function (width, height) {
let images = [];
let xLeftOver = width % 8;
let yLeftOver = height % 8;
let xRepeats = (width - xLeftOver) / 8;
let yRepeats = (height - yLeftOver) / 8;
for (let i = 0; i < imagePasses.length; i++) {
let pass = imagePasses[i];
let passWidth = xRepeats * pass.x.length;
let passHeight = yRepeats * pass.y.length;
for (let j = 0; j < pass.x.length; j++) {
if (pass.x[j] < xLeftOver) {
passWidth++;
} else {
break;
}
}
for (let j = 0; j < pass.y.length; j++) {
if (pass.y[j] < yLeftOver) {
passHeight++;
} else {
break;
}
}
if (passWidth > 0 && passHeight > 0) {
images.push({ width: passWidth, height: passHeight, index: i });
}
}
return images;
};
export var getInterlaceIterator = function (width) {
return function (x, y, pass) {
let outerXLeftOver = x % imagePasses[pass].x.length;
let outerX =
((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 +
imagePasses[pass].x[outerXLeftOver];
let outerYLeftOver = y % imagePasses[pass].y.length;
let outerY =
((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 +
imagePasses[pass].y[outerYLeftOver];
return outerX * 4 + outerY * width * 4;
};
};
export default {
getImagePasses,
getInterlaceIterator
}

View file

@ -1,51 +0,0 @@
let util = require("util");
let Stream = require("stream");
import constants from "./constants.js";
import Packer from "./packer.js";
let PackerAsync = function (opt) {
Stream.call(this);
let options = opt || {};
this._packer = new Packer(options);
this._deflate = this._packer.createDeflate();
this.readable = true;
};
util.inherits(PackerAsync, Stream);
export default PackerAsync;
PackerAsync.prototype.pack = function (data, width, height, gamma) {
// Signature
this.emit("data", Buffer.from(constants.PNG_SIGNATURE));
this.emit("data", this._packer.packIHDR(width, height));
if (gamma) {
this.emit("data", this._packer.packGAMA(gamma));
}
let filteredData = this._packer.filterData(data, width, height);
// compress it
this._deflate.on("error", this.emit.bind(this, "error"));
this._deflate.on(
"data",
function (compressedData) {
this.emit("data", this._packer.packIDAT(compressedData));
}.bind(this)
);
this._deflate.on(
"end",
function () {
this.emit("data", this._packer.packIEND());
this.emit("end");
}.bind(this)
);
this._deflate.end(filteredData);
};

View file

@ -1,52 +0,0 @@
import constants from "./constants.js";
import Packer from "./packer.js";
let hasSyncZlib = true;
let zlib = require("zlib");
export default function (metaData, opt) {
if (!hasSyncZlib) {
throw new Error(
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
);
}
let options = opt || {};
let packer = new Packer(options);
let chunks = [];
// Signature
chunks.push(Buffer.from(constants.PNG_SIGNATURE));
// Header
chunks.push(packer.packIHDR(metaData.width, metaData.height));
if (metaData.gamma) {
chunks.push(packer.packGAMA(metaData.gamma));
}
let filteredData = packer.filterData(
metaData.data,
metaData.width,
metaData.height
);
// compress it
let compressedData = zlib.deflateSync(
filteredData,
packer.getDeflateOptions()
);
filteredData = null;
if (!compressedData || !compressedData.length) {
throw new Error("bad png - invalid compressed data response");
}
chunks.push(packer.packIDAT(compressedData));
// End
chunks.push(packer.packIEND());
return Buffer.concat(chunks);
};

212
lib/packer.js Normal file → Executable file
View file

@ -1,130 +1,110 @@
import constants from "./constants.js";
import CrcStream from "./crc.js";
import bitPacker from "./bitpacker.js";
import filter from "./filter-pack.js";
// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
let zlib = require("zlib");
'use strict';
let Packer = function (options) {
this._options = options;
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
options.deflateLevel =
options.deflateLevel != null ? options.deflateLevel : 9;
options.deflateStrategy =
options.deflateStrategy != null ? options.deflateStrategy : 3;
options.inputHasAlpha =
options.inputHasAlpha != null ? options.inputHasAlpha : true;
options.deflateFactory = options.deflateFactory || zlib.createDeflate;
options.bitDepth = options.bitDepth || 8;
// This is outputColorType
options.colorType =
typeof options.colorType === "number"
? options.colorType
: constants.COLORTYPE_COLOR_ALPHA;
options.inputColorType =
typeof options.inputColorType === "number"
? options.inputColorType
: constants.COLORTYPE_COLOR_ALPHA;
var util = require('util'),
Stream = require('stream'),
zlib = require('zlib'),
Filter = require('./filter'),
CrcStream = require('./crc'),
constants = require('./constants');
if (
[
constants.COLORTYPE_GRAYSCALE,
constants.COLORTYPE_COLOR,
constants.COLORTYPE_COLOR_ALPHA,
constants.COLORTYPE_ALPHA,
].indexOf(options.colorType) === -1
) {
throw new Error(
"option color type:" + options.colorType + " is not supported at present"
);
}
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"
);
}
var Packer = module.exports = function(options) {
Stream.call(this);
this._options = options;
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
options.deflateLevel = options.deflateLevel || 9;
options.deflateStrategy = options.deflateStrategy || 3;
this.readable = true;
};
util.inherits(Packer, Stream);
Packer.prototype.pack = function(data, width, height) {
// Signature
this.emit('data', new Buffer(constants.PNG_SIGNATURE));
this.emit('data', this._packIHDR(width, height));
// filter pixel data
var filter = new Filter(width, height, 4, data, this._options);
var data = filter.filter();
// compress it
var deflate = zlib.createDeflate({
chunkSize: this._options.deflateChunkSize,
level: this._options.deflateLevel,
strategy: this._options.deflateStrategy
});
deflate.on('error', this.emit.bind(this, 'error'));
deflate.on('data', function(data) {
this.emit('data', this._packIDAT(data));
}.bind(this));
deflate.on('end', function() {
this.emit('data', this._packIEND());
this.emit('end');
}.bind(this));
deflate.end(data);
};
export default Packer;
Packer.prototype._packChunk = function(type, data) {
Packer.prototype.getDeflateOptions = function () {
return {
chunkSize: this._options.deflateChunkSize,
level: this._options.deflateLevel,
strategy: this._options.deflateStrategy,
};
var len = (data ? data.length : 0),
buf = new Buffer(len + 12);
buf.writeUInt32BE(len, 0);
buf.writeUInt32BE(type, 4);
if (data) data.copy(buf, 8);
buf.writeInt32BE(CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4);
return buf;
};
Packer.prototype.createDeflate = function () {
return this._options.deflateFactory(this.getDeflateOptions());
Packer.prototype._packIHDR = function(width, height) {
var buf = new Buffer(13);
buf.writeUInt32BE(width, 0);
buf.writeUInt32BE(height, 4);
buf[8] = 8;
buf[9] = 6; // colorType
buf[10] = 0; // compression
buf[11] = 0; // filter
buf[12] = 0; // interlace
return this._packChunk(constants.TYPE_IHDR, buf);
};
Packer.prototype.filterData = function (data, width, height) {
// convert to correct format for filtering (e.g. right bpp and bit depth)
let packedData = bitPacker(data, width, height, this._options);
// filter pixel data
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._options.colorType];
let filteredData = filter(packedData, width, height, this._options, bpp);
return filteredData;
Packer.prototype._packIDAT = function(data) {
return this._packChunk(constants.TYPE_IDAT, data);
};
Packer.prototype._packChunk = function (type, data) {
let len = data ? data.length : 0;
let buf = Buffer.alloc(len + 12);
buf.writeUInt32BE(len, 0);
buf.writeUInt32BE(type, 4);
if (data) {
data.copy(buf, 8);
}
buf.writeInt32BE(
CrcStream.crc32(buf.slice(4, buf.length - 4)),
buf.length - 4
);
return buf;
};
Packer.prototype.packGAMA = function (gamma) {
let buf = Buffer.alloc(4);
buf.writeUInt32BE(Math.floor(gamma * constants.GAMMA_DIVISION), 0);
return this._packChunk(constants.TYPE_gAMA, buf);
};
Packer.prototype.packIHDR = function (width, height) {
let buf = Buffer.alloc(13);
buf.writeUInt32BE(width, 0);
buf.writeUInt32BE(height, 4);
buf[8] = this._options.bitDepth; // Bit depth
buf[9] = this._options.colorType; // colorType
buf[10] = 0; // compression
buf[11] = 0; // filter
buf[12] = 0; // interlace
return this._packChunk(constants.TYPE_IHDR, buf);
};
Packer.prototype.packIDAT = function (data) {
return this._packChunk(constants.TYPE_IDAT, data);
};
Packer.prototype.packIEND = function () {
return this._packChunk(constants.TYPE_IEND, null);
Packer.prototype._packIEND = function() {
return this._packChunk(constants.TYPE_IEND, null);
};

View file

@ -1,14 +0,0 @@
export default function paethPredictor(left, above, upLeft) {
let paeth = left + above - upLeft;
let pLeft = Math.abs(paeth - left);
let pAbove = Math.abs(paeth - above);
let pUpLeft = Math.abs(paeth - upLeft);
if (pLeft <= pAbove && pLeft <= pUpLeft) {
return left;
}
if (pAbove <= pUpLeft) {
return above;
}
return upLeft;
};

View file

@ -1,166 +0,0 @@
import ChunkStream from "./chunkstream.js";
import FilterAsync from "./filter-parse-async.js";
import Parser from "./parser.js";
import bitmapper from "./bitmapper.js";
import formatNormaliser from "./format-normaliser.js";
let util = require("util");
let zlib = require("zlib");
let ParserAsync = function (options) {
ChunkStream.call(this);
this._parser = new Parser(options, {
read: this.read.bind(this),
error: this._handleError.bind(this),
metadata: this._handleMetaData.bind(this),
gamma: this.emit.bind(this, "gamma"),
palette: this._handlePalette.bind(this),
transColor: this._handleTransColor.bind(this),
finished: this._finished.bind(this),
inflateData: this._inflateData.bind(this),
simpleTransparency: this._simpleTransparency.bind(this),
headersFinished: this._headersFinished.bind(this),
});
this._options = options;
this.writable = true;
this._parser.start();
};
util.inherits(ParserAsync, ChunkStream);
export default ParserAsync;
ParserAsync.prototype._handleError = function (err) {
this.emit("error", err);
this.writable = false;
this.destroy();
if (this._inflate && this._inflate.destroy) {
this._inflate.destroy();
}
if (this._filter) {
this._filter.destroy();
// For backward compatibility with Node 7 and below.
// Suppress errors due to _inflate calling write() even after
// it's destroy()'ed.
this._filter.on("error", function () {});
}
this.errord = true;
};
ParserAsync.prototype._inflateData = function (data) {
if (!this._inflate) {
if (this._bitmapInfo.interlace) {
this._inflate = zlib.createInflate();
this._inflate.on("error", this.emit.bind(this, "error"));
this._filter.on("complete", this._complete.bind(this));
this._inflate.pipe(this._filter);
} else {
let rowSize =
((this._bitmapInfo.width *
this._bitmapInfo.bpp *
this._bitmapInfo.depth +
7) >>
3) +
1;
let imageSize = rowSize * this._bitmapInfo.height;
let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK);
this._inflate = zlib.createInflate({ chunkSize: chunkSize });
let leftToInflate = imageSize;
let emitError = this.emit.bind(this, "error");
this._inflate.on("error", function (err) {
if (!leftToInflate) {
return;
}
emitError(err);
});
this._filter.on("complete", this._complete.bind(this));
let filterWrite = this._filter.write.bind(this._filter);
this._inflate.on("data", function (chunk) {
if (!leftToInflate) {
return;
}
if (chunk.length > leftToInflate) {
chunk = chunk.slice(0, leftToInflate);
}
leftToInflate -= chunk.length;
filterWrite(chunk);
});
this._inflate.on("end", this._filter.end.bind(this._filter));
}
}
this._inflate.write(data);
};
ParserAsync.prototype._handleMetaData = function (metaData) {
this._metaData = metaData;
this._bitmapInfo = Object.create(metaData);
this._filter = new FilterAsync(this._bitmapInfo);
};
ParserAsync.prototype._handleTransColor = function (transColor) {
this._bitmapInfo.transColor = transColor;
};
ParserAsync.prototype._handlePalette = function (palette) {
this._bitmapInfo.palette = palette;
};
ParserAsync.prototype._simpleTransparency = function () {
this._metaData.alpha = true;
};
ParserAsync.prototype._headersFinished = function () {
// Up until this point, we don't know if we have a tRNS chunk (alpha)
// so we can't emit metadata any earlier
this.emit("metadata", this._metaData);
};
ParserAsync.prototype._finished = function () {
if (this.errord) {
return;
}
if (!this._inflate) {
this.emit("error", "No Inflate block");
} else {
// no more data to inflate
this._inflate.end();
}
};
ParserAsync.prototype._complete = function (filteredData) {
if (this.errord) {
return;
}
let normalisedBitmapData;
try {
let bitmapData = bitmapper(filteredData, this._bitmapInfo);
normalisedBitmapData = formatNormaliser(bitmapData, this._bitmapInfo);
bitmapData = null;
} catch (ex) {
this._handleError(ex);
return;
}
this.emit("parsed", normalisedBitmapData);
};

View file

@ -1,107 +0,0 @@
import inflateSync from "./sync-inflate.js";
import SyncReader from "./sync-reader.js";
import FilterSync from "./filter-parse-sync.js";
import bitmapper from "./bitmapper.js";
import formatNormaliser from "./format-normaliser.js";
import Parser from "./parser.js";
let hasSyncZlib = true;
let zlib = require("zlib");
if (!zlib.deflateSync) {
hasSyncZlib = false;
}
export default function (buffer, options) {
if (!hasSyncZlib) {
throw new Error(
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
);
}
let err;
function handleError(_err_) {
err = _err_;
}
let metaData;
function handleMetaData(_metaData_) {
metaData = _metaData_;
}
function handleTransColor(transColor) {
metaData.transColor = transColor;
}
function handlePalette(palette) {
metaData.palette = palette;
}
function handleSimpleTransparency() {
metaData.alpha = true;
}
let gamma;
function handleGamma(_gamma_) {
gamma = _gamma_;
}
let inflateDataList = [];
function handleInflateData(inflatedData) {
inflateDataList.push(inflatedData);
}
let reader = new SyncReader(buffer);
let parser = new Parser(options, {
read: reader.read.bind(reader),
error: handleError,
metadata: handleMetaData,
gamma: handleGamma,
palette: handlePalette,
transColor: handleTransColor,
inflateData: handleInflateData,
simpleTransparency: handleSimpleTransparency,
});
parser.start();
reader.process();
if (err) {
throw err;
}
//join together the inflate datas
let inflateData = Buffer.concat(inflateDataList);
inflateDataList.length = 0;
let inflatedData;
if (metaData.interlace) {
inflatedData = zlib.inflateSync(inflateData);
} else {
let rowSize =
((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1;
let imageSize = rowSize * metaData.height;
inflatedData = inflateSync(inflateData, {
chunkSize: imageSize,
maxLength: imageSize,
});
}
inflateData = null;
if (!inflatedData || !inflatedData.length) {
throw new Error("bad png - invalid inflate data response");
}
let unfilteredData = FilterSync.process(inflatedData, metaData);
inflateData = null;
let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData);
unfilteredData = null;
let normalisedBitmapData = formatNormaliser(bitmapData, metaData);
metaData.data = normalisedBitmapData;
metaData.gamma = gamma || 0;
return metaData;
};

575
lib/parser.js Normal file → Executable file
View file

@ -1,290 +1,359 @@
"use strict";
// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
let constants = require("./constants");
let CrcCalculator = require("./crc");
'use strict';
let Parser = (module.exports = function (options, dependencies) {
this._options = options;
options.checkCRC = options.checkCRC !== false;
this._hasIHDR = false;
this._hasIEND = false;
this._emittedHeadersFinished = false;
var util = require('util'),
zlib = require('zlib'),
CrcStream = require('./crc'),
ChunkStream = require('./chunkstream'),
constants = require('./constants'),
Filter = require('./filter');
// input flags/metadata
this._palette = [];
this._colorType = 0;
this._chunks = {};
this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
var Parser = module.exports = function(options) {
ChunkStream.call(this);
this.read = dependencies.read;
this.error = dependencies.error;
this.metadata = dependencies.metadata;
this.gamma = dependencies.gamma;
this.transColor = dependencies.transColor;
this.palette = dependencies.palette;
this.parsed = dependencies.parsed;
this.inflateData = dependencies.inflateData;
this.finished = dependencies.finished;
this.simpleTransparency = dependencies.simpleTransparency;
this.headersFinished = dependencies.headersFinished || function () {};
});
this._options = options;
options.checkCRC = options.checkCRC !== false;
Parser.prototype.start = function () {
this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
this._hasIHDR = false;
this._hasIEND = false;
this._inflate = null;
this._filter = null;
this._crc = null;
// input flags/metadata
this._palette = [];
this._colorType = 0;
this._chunks = {};
this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
this.writable = true;
this.on('error', this._handleError.bind(this));
this._handleSignature();
};
util.inherits(Parser, ChunkStream);
Parser.prototype._handleError = function() {
this.writable = false;
this.destroy();
if (this._inflate)
this._inflate.destroy();
};
Parser.prototype._parseSignature = function (data) {
let signature = constants.PNG_SIGNATURE;
Parser.prototype._handleSignature = function() {
this.read(constants.PNG_SIGNATURE.length,
this._parseSignature.bind(this)
);
};
for (let i = 0; i < signature.length; i++) {
if (data[i] !== signature[i]) {
this.error(new Error("Invalid file signature"));
return;
Parser.prototype._parseSignature = function(data) {
var signature = constants.PNG_SIGNATURE;
for (var i = 0; i < signature.length; i++) {
if (data[i] != signature[i]) {
this.emit('error', new Error('Invalid file signature'));
return;
}
}
}
this.read(8, this._parseChunkBegin.bind(this));
};
Parser.prototype._parseChunkBegin = function (data) {
// chunk content length
let length = data.readUInt32BE(0);
// chunk type
let type = data.readUInt32BE(4);
let name = "";
for (let i = 4; i < 8; i++) {
name += String.fromCharCode(data[i]);
}
//console.log('chunk ', name, length);
// chunk flags
let ancillary = Boolean(data[4] & 0x20); // or critical
// priv = Boolean(data[5] & 0x20), // or public
// safeToCopy = Boolean(data[7] & 0x20); // or unsafe
if (!this._hasIHDR && type !== constants.TYPE_IHDR) {
this.error(new Error("Expected IHDR on beggining"));
return;
}
this._crc = new CrcCalculator();
this._crc.write(Buffer.from(name));
if (this._chunks[type]) {
return this._chunks[type](length);
}
if (!ancillary) {
this.error(new Error("Unsupported critical chunk type " + name));
return;
}
this.read(length + 4, this._skipChunk.bind(this));
};
Parser.prototype._skipChunk = function (/*data*/) {
this.read(8, this._parseChunkBegin.bind(this));
};
Parser.prototype._handleChunkEnd = function () {
this.read(4, this._parseChunkEnd.bind(this));
};
Parser.prototype._parseChunkEnd = function (data) {
let fileCrc = data.readInt32BE(0);
let calcCrc = this._crc.crc32();
// check CRC
if (this._options.checkCRC && calcCrc !== fileCrc) {
this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc));
return;
}
if (!this._hasIEND) {
this.read(8, this._parseChunkBegin.bind(this));
}
};
Parser.prototype._handleIHDR = function (length) {
this.read(length, this._parseIHDR.bind(this));
};
Parser.prototype._parseIHDR = function (data) {
this._crc.write(data);
Parser.prototype._parseChunkBegin = function(data) {
let width = data.readUInt32BE(0);
let height = data.readUInt32BE(4);
let depth = data[8];
let colorType = data[9]; // bits: 1 palette, 2 color, 4 alpha
let compr = data[10];
let filter = data[11];
let interlace = data[12];
// chunk content length
var length = data.readUInt32BE(0);
// console.log(' width', width, 'height', height,
// 'depth', depth, 'colorType', colorType,
// 'compr', compr, 'filter', filter, 'interlace', interlace
// );
// chunk type
var type = data.readUInt32BE(4),
name = '';
for (var i = 4; i < 8; i++)
name += String.fromCharCode(data[i]);
if (
depth !== 8 &&
depth !== 4 &&
depth !== 2 &&
depth !== 1 &&
depth !== 16
) {
this.error(new Error("Unsupported bit depth " + depth));
return;
}
if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) {
this.error(new Error("Unsupported color type"));
return;
}
if (compr !== 0) {
this.error(new Error("Unsupported compression method"));
return;
}
if (filter !== 0) {
this.error(new Error("Unsupported filter method"));
return;
}
if (interlace !== 0 && interlace !== 1) {
this.error(new Error("Unsupported interlace method"));
return;
}
// console.log('chunk ', name, length);
this._colorType = colorType;
// chunk flags
var ancillary = !!(data[4] & 0x20), // or critical
priv = !!(data[5] & 0x20), // or public
safeToCopy = !!(data[7] & 0x20); // or unsafe
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
this._hasIHDR = true;
this.metadata({
width: width,
height: height,
depth: depth,
interlace: Boolean(interlace),
palette: Boolean(colorType & constants.COLORTYPE_PALETTE),
color: Boolean(colorType & constants.COLORTYPE_COLOR),
alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),
bpp: bpp,
colorType: colorType,
});
this._handleChunkEnd();
};
Parser.prototype._handlePLTE = function (length) {
this.read(length, this._parsePLTE.bind(this));
};
Parser.prototype._parsePLTE = function (data) {
this._crc.write(data);
let entries = Math.floor(data.length / 3);
// console.log('Palette:', entries);
for (let i = 0; i < entries; i++) {
this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]);
}
this.palette(this._palette);
this._handleChunkEnd();
};
Parser.prototype._handleTRNS = function (length) {
this.simpleTransparency();
this.read(length, this._parseTRNS.bind(this));
};
Parser.prototype._parseTRNS = function (data) {
this._crc.write(data);
// palette
if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {
if (this._palette.length === 0) {
this.error(new Error("Transparency chunk must be after palette"));
return;
if (!this._hasIHDR && type != constants.TYPE_IHDR) {
this.emit('error', new Error('Expected IHDR on beggining'));
return;
}
if (data.length > this._palette.length) {
this.error(new Error("More transparent colors than palette size"));
return;
this._crc = new CrcStream();
this._crc.write(new Buffer(name));
if (this._chunks[type]) {
return this._chunks[type](length);
} else if (!ancillary) {
this.emit('error', new Error('Unsupported critical chunk type ' + name));
return;
} else {
this.read(length + 4, this._skipChunk.bind(this));
}
for (let i = 0; i < data.length; i++) {
this._palette[i][3] = data[i];
};
Parser.prototype._skipChunk = function(data) {
this.read(8, this._parseChunkBegin.bind(this));
};
Parser.prototype._handleChunkEnd = function() {
this.read(4, this._parseChunkEnd.bind(this));
};
Parser.prototype._parseChunkEnd = function(data) {
var fileCrc = data.readInt32BE(0),
calcCrc = this._crc.crc32();
// check CRC
if (this._options.checkCRC && calcCrc != fileCrc) {
this.emit('error', new Error('Crc error'));
return;
}
this.palette(this._palette);
}
// for colorType 0 (grayscale) and 2 (rgb)
// there might be one gray/color defined as transparent
if (this._colorType === constants.COLORTYPE_GRAYSCALE) {
// grey, 2 bytes
this.transColor([data.readUInt16BE(0)]);
}
if (this._colorType === constants.COLORTYPE_COLOR) {
this.transColor([
data.readUInt16BE(0),
data.readUInt16BE(2),
data.readUInt16BE(4),
]);
}
if (this._hasIEND) {
this.destroySoon();
this._handleChunkEnd();
} else {
this.read(8, this._parseChunkBegin.bind(this));
}
};
Parser.prototype._handleGAMA = function (length) {
this.read(length, this._parseGAMA.bind(this));
Parser.prototype._handleIHDR = function(length) {
this.read(length, this._parseIHDR.bind(this));
};
Parser.prototype._parseGAMA = function (data) {
this._crc.write(data);
this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);
Parser.prototype._parseIHDR = function(data) {
this._handleChunkEnd();
};
this._crc.write(data);
Parser.prototype._handleIDAT = function (length) {
if (!this._emittedHeadersFinished) {
this._emittedHeadersFinished = true;
this.headersFinished();
}
this.read(-length, this._parseIDAT.bind(this, length));
};
Parser.prototype._parseIDAT = function (length, data) {
this._crc.write(data);
var width = data.readUInt32BE(0),
height = data.readUInt32BE(4),
depth = data[8],
colorType = data[9], // bits: 1 palette, 2 color, 4 alpha
compr = data[10],
filter = data[11],
interlace = data[12];
if (
this._colorType === constants.COLORTYPE_PALETTE_COLOR &&
this._palette.length === 0
) {
throw new Error("Expected palette not found");
}
// console.log(' width', width, 'height', height,
// 'depth', depth, 'colorType', colorType,
// 'compr', compr, 'filter', filter, 'interlace', interlace
// );
this.inflateData(data);
let leftOverLength = length - data.length;
if (depth != 8) {
this.emit('error', new Error('Unsupported bit depth ' + depth));
return;
}
if (!(colorType in colorTypeToBppMap)) {
this.emit('error', new Error('Unsupported color type'));
return;
}
if (compr != 0) {
this.emit('error', new Error('Unsupported compression method'));
return;
}
if (filter != 0) {
this.emit('error', new Error('Unsupported filter method'));
return;
}
if (interlace != 0) {
this.emit('error', new Error('Unsupported interlace method'));
return;
}
this._colorType = colorType;
this._data = new Buffer(width * height * 4);
this._filter = new Filter(
width, height,
colorTypeToBppMap[this._colorType],
this._data,
this._options
);
this._hasIHDR = true;
this.emit('metadata', {
width: width,
height: height,
palette: !!(colorType & constants.COLOR_PALETTE),
color: !!(colorType & constants.COLOR_COLOR),
alpha: !!(colorType & constants.COLOR_ALPHA),
data: this._data
});
if (leftOverLength > 0) {
this._handleIDAT(leftOverLength);
} else {
this._handleChunkEnd();
}
};
Parser.prototype._handleIEND = function (length) {
this.read(length, this._parseIEND.bind(this));
};
Parser.prototype._parseIEND = function (data) {
this._crc.write(data);
this._hasIEND = true;
this._handleChunkEnd();
if (this.finished) {
this.finished();
}
Parser.prototype._handlePLTE = function(length) {
this.read(length, this._parsePLTE.bind(this));
};
Parser.prototype._parsePLTE = function(data) {
this._crc.write(data);
var entries = Math.floor(data.length / 3);
// console.log('Palette:', entries);
for (var i = 0; i < entries; i++) {
this._palette.push([
data.readUInt8(i * 3),
data.readUInt8(i * 3 + 1),
data.readUInt8(i * 3 + 2 ),
0xff
]);
}
this._handleChunkEnd();
};
Parser.prototype._handleTRNS = function(length) {
this.read(length, this._parseTRNS.bind(this));
};
Parser.prototype._parseTRNS = function(data) {
this._crc.write(data);
// palette
if (this._colorType == 3) {
if (this._palette.length == 0) {
this.emit('error', new Error('Transparency chunk must be after palette'));
return;
}
if (data.length > this._palette.length) {
this.emit('error', new Error('More transparent colors than palette size'));
return;
}
for (var i = 0; i < this._palette.length; i++) {
this._palette[i][3] = i < data.length ? data.readUInt8(i) : 0xff;
}
}
// for colorType 0 (grayscale) and 2 (rgb)
// there might be one gray/color defined as transparent
this._handleChunkEnd();
};
Parser.prototype._handleGAMA = function(length) {
this.read(length, this._parseGAMA.bind(this));
};
Parser.prototype._parseGAMA = function(data) {
this._crc.write(data);
this.emit('gamma', data.readUInt32BE(0) / 100000);
this._handleChunkEnd();
};
Parser.prototype._handleIDAT = function(length) {
this.read(-length, this._parseIDAT.bind(this, length));
};
Parser.prototype._parseIDAT = function(length, data) {
this._crc.write(data);
if (this._colorType == 3 && this._palette.length == 0)
throw new Error('Expected palette not found');
if (!this._inflate) {
this._inflate = zlib.createInflate();
this._inflate.on('error', this.emit.bind(this, 'error'));
this._filter.on('complete', this._reverseFiltered.bind(this));
this._inflate.pipe(this._filter);
}
this._inflate.write(data);
length -= data.length;
if (length > 0)
this._handleIDAT(length);
else
this._handleChunkEnd();
};
Parser.prototype._handleIEND = function(length) {
this.read(length, this._parseIEND.bind(this));
};
Parser.prototype._parseIEND = function(data) {
this._crc.write(data);
// no more data to inflate
this._inflate.end();
this._hasIEND = true;
this._handleChunkEnd();
};
var colorTypeToBppMap = {
0: 1,
2: 3,
3: 1,
4: 2,
6: 4
};
Parser.prototype._reverseFiltered = function(data, width, height) {
if (this._colorType == 3) { // paletted
// use values from palette
var pxLineLength = width << 2;
for (var y = 0; y < height; y++) {
var pxRowPos = y * pxLineLength;
for (var x = 0; x < width; x++) {
var pxPos = pxRowPos + (x << 2),
color = this._palette[data[pxPos]];
for (var i = 0; i < 4; i++)
data[pxPos + i] = color[i];
}
}
}
this.emit('parsed', data);
};

View file

@ -1,15 +0,0 @@
import parse from "./parser-sync.js";
import pack from "./packer-sync.js";
export var read = function (buffer, options) {
return parse(buffer, options || {});
};
export var write = function (png, options) {
return pack(png, options);
};
export default {
read,
write
}

288
lib/png.js Normal file → Executable file
View file

@ -1,193 +1,149 @@
import Parser from "./parser-async.js";
import Packer from "./packer-async.js";
import PNGSync from "./png-sync.js";
// Copyright (c) 2012 Kuba Niegowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
let util = require("util");
let Stream = require("stream");
'use strict';
let PNG = (exports.PNG = function (options) {
Stream.call(this);
options = options || {}; // eslint-disable-line no-param-reassign
var util = require('util'),
Stream = require('stream'),
Parser = require('./parser'),
Packer = require('./packer');
// coerce pixel dimensions to integers (also coerces undefined -> 0):
this.width = options.width | 0;
this.height = options.height | 0;
this.data =
this.width > 0 && this.height > 0
? Buffer.alloc(4 * this.width * this.height)
: null;
var PNG = exports.PNG = function(options) {
Stream.call(this);
if (options.fill && this.data) {
this.data.fill(0);
}
options = options || {};
this.gamma = 0;
this.readable = this.writable = true;
this.width = options.width || 0;
this.height = options.height || 0;
this.data = this.width > 0 && this.height > 0
? new Buffer(4 * this.width * this.height) : null;
this._parser = new Parser(options);
if(options.fill && this.data){this.data.fill(0)};
this._parser.on("error", this.emit.bind(this, "error"));
this._parser.on("close", this._handleClose.bind(this));
this._parser.on("metadata", this._metadata.bind(this));
this._parser.on("gamma", this._gamma.bind(this));
this._parser.on(
"parsed",
function (data) {
this.data = data;
this.emit("parsed", data);
}.bind(this)
);
this.gamma = 0;
this.readable = this.writable = true;
this._packer = new Packer(options);
this._packer.on("data", this.emit.bind(this, "data"));
this._packer.on("end", this.emit.bind(this, "end"));
this._parser.on("close", this._handleClose.bind(this));
this._packer.on("error", this.emit.bind(this, "error"));
});
this._parser = new Parser(options || {});
this._parser.on('error', this.emit.bind(this, 'error'));
this._parser.on('close', this._handleClose.bind(this));
this._parser.on('metadata', this._metadata.bind(this));
this._parser.on('gamma', this._gamma.bind(this));
this._parser.on('parsed', function(data) {
this.data = data;
this.emit('parsed', data);
}.bind(this));
this._packer = new Packer(options);
this._packer.on('data', this.emit.bind(this, 'data'));
this._packer.on('end', this.emit.bind(this, 'end'));
this._parser.on('close', this._handleClose.bind(this));
this._packer.on('error', this.emit.bind(this, 'error'));
};
util.inherits(PNG, Stream);
PNG.sync = PNGSync;
PNG.prototype.pack = function () {
if (!this.data || !this.data.length) {
this.emit("error", "No data provided");
PNG.prototype.pack = function() {
process.nextTick(function() {
this._packer.pack(this.data, this.width, this.height);
}.bind(this));
return this;
}
process.nextTick(
function () {
this._packer.pack(this.data, this.width, this.height, this.gamma);
}.bind(this)
);
return this;
};
PNG.prototype.parse = function (data, callback) {
if (callback) {
let onParsed, onError;
onParsed = function (parsedData) {
this.removeListener("error", onError);
PNG.prototype.parse = function(data, callback) {
this.data = parsedData;
callback(null, this);
}.bind(this);
if (callback) {
var onParsed = null, onError = null;
onError = function (err) {
this.removeListener("parsed", onParsed);
this.once('parsed', onParsed = function(data) {
this.removeListener('error', onError);
callback(err, null);
}.bind(this);
this.data = data;
callback(null, this);
this.once("parsed", onParsed);
this.once("error", onError);
}
}.bind(this));
this.end(data);
return this;
};
this.once('error', onError = function(err) {
this.removeListener('parsed', onParsed);
PNG.prototype.write = function (data) {
this._parser.write(data);
return true;
};
PNG.prototype.end = function (data) {
this._parser.end(data);
};
PNG.prototype._metadata = function (metadata) {
this.width = metadata.width;
this.height = metadata.height;
this.emit("metadata", metadata);
};
PNG.prototype._gamma = function (gamma) {
this.gamma = gamma;
};
PNG.prototype._handleClose = function () {
if (!this._parser.writable && !this._packer.readable) {
this.emit("close");
}
};
PNG.bitblt = function (src, dst, srcX, srcY, width, height, deltaX, deltaY) {
// eslint-disable-line max-params
// coerce pixel dimensions to integers (also coerces undefined -> 0):
/* eslint-disable no-param-reassign */
srcX |= 0;
srcY |= 0;
width |= 0;
height |= 0;
deltaX |= 0;
deltaY |= 0;
/* eslint-enable no-param-reassign */
if (
srcX > src.width ||
srcY > src.height ||
srcX + width > src.width ||
srcY + height > src.height
) {
throw new Error("bitblt reading outside image");
}
if (
deltaX > dst.width ||
deltaY > dst.height ||
deltaX + width > dst.width ||
deltaY + height > dst.height
) {
throw new Error("bitblt writing outside image");
}
for (let y = 0; y < height; y++) {
src.data.copy(
dst.data,
((deltaY + y) * dst.width + deltaX) << 2,
((srcY + y) * src.width + srcX) << 2,
((srcY + y) * src.width + srcX + width) << 2
);
}
};
PNG.prototype.bitblt = function (
dst,
srcX,
srcY,
width,
height,
deltaX,
deltaY
) {
// eslint-disable-line max-params
PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY);
return this;
};
PNG.adjustGamma = function (src) {
if (src.gamma) {
for (let y = 0; y < src.height; y++) {
for (let x = 0; x < src.width; x++) {
let idx = (src.width * y + x) << 2;
for (let i = 0; i < 3; i++) {
let sample = src.data[idx + i] / 255;
sample = Math.pow(sample, 1 / 2.2 / src.gamma);
src.data[idx + i] = Math.round(sample * 255);
}
}
callback(err, null);
}.bind(this));
}
src.gamma = 0;
}
this.end(data);
return this;
};
PNG.prototype.adjustGamma = function () {
PNG.adjustGamma(this);
PNG.prototype.write = function(data) {
this._parser.write(data);
return true;
};
PNG.prototype.end = function(data) {
this._parser.end(data);
};
PNG.prototype._metadata = function(metadata) {
this.width = metadata.width;
this.height = metadata.height;
this.data = metadata.data;
delete metadata.data;
this.emit('metadata', metadata);
};
PNG.prototype._gamma = function(gamma) {
this.gamma = gamma;
};
PNG.prototype._handleClose = function() {
if (!this._parser.writable && !this._packer.readable)
this.emit('close');
};
PNG.prototype.bitblt = function(dst, sx, sy, w, h, dx, dy) {
var src = this;
if (sx > src.width || sy > src.height
|| sx + w > src.width || sy + h > src.height)
throw new Error('bitblt reading outside image');
if (dx > dst.width || dy > dst.height
|| dx + w > dst.width || dy + h > dst.height)
throw new Error('bitblt writing outside image');
for (var y = 0; y < h; y++) {
src.data.copy(dst.data,
((dy + y) * dst.width + dx) << 2,
((sy + y) * src.width + sx) << 2,
((sy + y) * src.width + sx + w) << 2
);
}
return this;
};

View file

@ -1,163 +0,0 @@
let assert = require("assert").ok;
let zlib = require("zlib");
let util = require("util");
let kMaxLength = require("buffer").kMaxLength;
export function Inflate(opts) {
if (!(this instanceof Inflate)) {
return new Inflate(opts);
}
if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) {
opts.chunkSize = zlib.Z_MIN_CHUNK;
}
zlib.Inflate.call(this, opts);
// Node 8 --> 9 compatibility check
this._offset = this._offset === undefined ? this._outOffset : this._offset;
this._buffer = this._buffer || this._outBuffer;
if (opts && opts.maxLength != null) {
this._maxLength = opts.maxLength;
}
}
export function createInflate(opts) {
return new Inflate(opts);
}
function _close(engine, callback) {
if (callback) {
process.nextTick(callback);
}
// Caller may invoke .close after a zlib error (which will null _handle).
if (!engine._handle) {
return;
}
engine._handle.close();
engine._handle = null;
}
Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) {
if (typeof asyncCb === "function") {
return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb);
}
let self = this;
let availInBefore = chunk && chunk.length;
let availOutBefore = this._chunkSize - this._offset;
let leftToInflate = this._maxLength;
let inOff = 0;
let buffers = [];
let nread = 0;
let error;
this.on("error", function (err) {
error = err;
});
function handleChunk(availInAfter, availOutAfter) {
if (self._hadError) {
return;
}
let have = availOutBefore - availOutAfter;
assert(have >= 0, "have should not go down");
if (have > 0) {
let out = self._buffer.slice(self._offset, self._offset + have);
self._offset += have;
if (out.length > leftToInflate) {
out = out.slice(0, leftToInflate);
}
buffers.push(out);
nread += out.length;
leftToInflate -= out.length;
if (leftToInflate === 0) {
return false;
}
}
if (availOutAfter === 0 || self._offset >= self._chunkSize) {
availOutBefore = self._chunkSize;
self._offset = 0;
self._buffer = Buffer.allocUnsafe(self._chunkSize);
}
if (availOutAfter === 0) {
inOff += availInBefore - availInAfter;
availInBefore = availInAfter;
return true;
}
return false;
}
assert(this._handle, "zlib binding closed");
let res;
do {
res = this._handle.writeSync(
flushFlag,
chunk, // in
inOff, // in_off
availInBefore, // in_len
this._buffer, // out
this._offset, //out_off
availOutBefore
); // out_len
// Node 8 --> 9 compatibility check
res = res || this._writeState;
} while (!this._hadError && handleChunk(res[0], res[1]));
if (this._hadError) {
throw error;
}
if (nread >= kMaxLength) {
_close(this);
throw new RangeError(
"Cannot create final Buffer. It would be larger than 0x" +
kMaxLength.toString(16) +
" bytes"
);
}
let buf = Buffer.concat(buffers, nread);
_close(this);
return buf;
};
util.inherits(Inflate, zlib.Inflate);
function zlibBufferSync(engine, buffer) {
if (typeof buffer === "string") {
buffer = Buffer.from(buffer);
}
if (!(buffer instanceof Buffer)) {
throw new TypeError("Not a string or buffer");
}
let flushFlag = engine._finishFlushFlag;
if (flushFlag == null) {
flushFlag = zlib.Z_FINISH;
}
return engine._processChunk(buffer, flushFlag);
}
export function inflateSync(buffer, opts) {
return zlibBufferSync(new Inflate(opts), buffer);
}
export default inflateSync;

View file

@ -1,45 +0,0 @@
let SyncReader = function (buffer) {
this._buffer = buffer;
this._reads = [];
};
export default SyncReader;
SyncReader.prototype.read = function (length, callback) {
this._reads.push({
length: Math.abs(length), // if length < 0 then at most this length
allowLess: length < 0,
func: callback,
});
};
SyncReader.prototype.process = function () {
// as long as there is any data and read requests
while (this._reads.length > 0 && this._buffer.length) {
let read = this._reads[0];
if (
this._buffer.length &&
(this._buffer.length >= read.length || read.allowLess)
) {
// ok there is any data so that we can satisfy this request
this._reads.shift(); // == read
let buf = this._buffer;
this._buffer = buf.slice(read.length);
read.func.call(this, buf.slice(0, read.length));
} else {
break;
}
}
if (this._reads.length > 0) {
return new Error("There are some read requests waitng on finished stream");
}
if (this._buffer.length > 0) {
return new Error("unrecognised content at end of stream");
}
};

View file

@ -1,73 +1,29 @@
{
"name": "pngjs",
"version": "5.0.0",
"description": "PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.",
"contributors": [
"Alexandre Paré",
"Gaurav Mali",
"Gusts Kaksis",
"Kuba Niegowski",
"Luke Page",
"Pietajan De Potter",
"Steven Sojka",
"liangzeng",
"Michael Vogt",
"Xin-Xin Wang",
"toriningen",
"Eugene Kulabuhov"
],
"homepage": "https://github.com/lukeapage/pngjs",
"name": "node-png",
"version": "0.4.3",
"description": "Simple PNG encoder/decoder",
"author": "Kuba Niegowski",
"contributors": [],
"homepage": "https://github.com/niegowski/node-pngjs/",
"keywords": [
"PNG",
"decoder",
"encoder",
"js-png",
"node-png",
"parser",
"png",
"png-js",
"png-parse",
"pngjs"
"png"
],
"engines": {
"node": ">=10.13.0"
"node": "0.8.x"
},
"main": "./lib/png.js",
"directories": {
"lib": "lib",
"example": "examples",
"test": "test"
"example": "examples"
},
"scripts": {
"build": "yarn prepublish",
"prepublish": "yarn browserify",
"browserify": "browserify lib/png.js --standalone png > browser.js",
"coverage": "nyc --reporter=lcov --reporter=text-summary tape test/*-spec.js nolarge",
"test": "yarn lint && yarn prettier:check && tape test/*-spec.js | tap-dot && node test/run-compare",
"lint": "eslint .",
"prettier:write": "prettier --write .",
"prettier:check": "prettier --check ."
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git://github.com/lukeapage/pngjs.git"
"url": "git://github.com/brighthas/node-pngjs.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/lukeapage/pngjs/issues"
},
"devDependencies": {
"browserify": "16.5.1",
"buffer-equal": "1.0.0",
"codecov": "3.7.0",
"connect": "3.7.0",
"eslint": "7.0.0",
"eslint-config-prettier": "6.11.0",
"nyc": "15.0.1",
"prettier": "2.0.5",
"puppeteer": "3.0.2",
"serve-static": "1.14.1",
"tap-dot": "2.0.0",
"tape": "5.0.0"
"url": "https://github.com/brighthas/node-pngjs/issues"
}
}

1
test/.gitignore vendored
View file

@ -1 +0,0 @@
bg.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

View file

@ -1,46 +0,0 @@
#!/usr/bin/env node
let fs = require("fs");
let PNG = require("../lib/png").PNG;
let test = require("tape");
let bufferEqual = require("buffer-equal");
test("outputs background, created from scratch", function (t) {
t.timeoutAfter(1000 * 60 * 5);
let png = new PNG({
width: 10,
height: 10,
filterType: -1,
});
for (let y = 0; y < png.height; y++) {
for (let x = 0; x < png.width; x++) {
let idx = (png.width * y + x) << 2;
let col = (x < png.width >> 1) ^ (y < png.height >> 1) ? 0xe5 : 0xff;
png.data[idx] = col;
png.data[idx + 1] = col;
png.data[idx + 2] = col;
png.data[idx + 3] = 0xff;
}
}
png
.pack()
.pipe(fs.createWriteStream(__dirname + "/bg.png"))
.on("finish", function () {
let out = fs.readFileSync(__dirname + "/bg.png");
let ref = fs.readFileSync(__dirname + "/bg-ref.png");
let isBufferEqual = bufferEqual(out, ref);
t.ok(isBufferEqual, "compares with working file ok");
if (!isBufferEqual) {
console.log(out.length, ref.length);
}
t.end();
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

View file

@ -1,105 +0,0 @@
let fs = require("fs");
let PNG = require("../lib/png").PNG;
let test = require("tape");
let noLargeOption = process.argv.indexOf("nolarge") >= 0;
fs.readdir(__dirname + "/in/", function (err, files) {
if (err) throw err;
files = files.filter(function (file) {
return (
(!noLargeOption || !file.match(/large/i)) &&
Boolean(file.match(/\.png$/i))
);
});
console.log("Converting images");
files.forEach(function (file) {
let expectedError = false;
if (file.match(/^x/)) {
expectedError = true;
}
test("convert sync - " + file, function (t) {
t.timeoutAfter(1000 * 60 * 5);
let data = fs.readFileSync(__dirname + "/in/" + file);
let png;
try {
png = PNG.sync.read(data);
} catch (e) {
if (!expectedError) {
t.fail(
"Unexpected error parsing.." +
file +
"\n" +
e.message +
"\n" +
e.stack
);
} else {
t.pass("completed");
}
return t.end();
}
if (expectedError) {
t.fail("Sync: Error expected, parsed fine .. - " + file);
return t.end();
}
let outpng = new PNG();
outpng.gamma = png.gamma;
outpng.data = png.data;
outpng.width = png.width;
outpng.height = png.height;
outpng.pack().pipe(
fs
.createWriteStream(__dirname + "/outsync/" + file)
.on("finish", function () {
t.pass("completed");
t.end();
})
);
});
test("convert async - " + file, function (t) {
t.timeoutAfter(1000 * 60 * 5);
fs.createReadStream(__dirname + "/in/" + file)
.pipe(new PNG())
.on("error", function (err) {
if (!expectedError) {
t.fail(
"Async: Unexpected error parsing.." +
file +
"\n" +
err.message +
"\n" +
err.stack
);
} else {
t.pass("completed");
}
t.end();
})
.on("parsed", function () {
if (expectedError) {
t.fail("Async: Error expected, parsed fine .." + file);
return t.end();
}
this.pack().pipe(
fs
.createWriteStream(__dirname + "/out/" + file)
.on("finish", function () {
t.pass("completed");
t.end();
})
);
});
});
});
});

View file

@ -1,16 +0,0 @@
let serveStatic = require("serve-static");
let http = require("http");
let connect = require("connect");
let app = connect();
let server = http.createServer(app);
app.use(serveStatic("test"));
server.listen(8000);
module.exports = () => {
server.close();
};
console.log("Tests available at http://localhost:8000/");

View file

@ -1,409 +0,0 @@
// js-imagediff 1.0.3
// (c) 2011-2012 Carl Sutherland, Humble Software
// Distributed under the MIT License
// For original source and documentation visit:
// http://www.github.com/HumbleSoftware/js-imagediff
(function (name, definition) {
var root = this;
if (typeof module !== 'undefined') {
try {
var Canvas = require('canvas');
} catch (e) {
throw new Error(
e.message + '\n' +
'Please see https://github.com/HumbleSoftware/js-imagediff#cannot-find-module-canvas\n'
);
}
module.exports = definition(root, name, Canvas);
} else if (typeof define === 'function' && typeof define.amd === 'object') {
define(definition);
} else {
root[name] = definition(root, name);
}
})('imagediff', function (root, name, Canvas) {
var
TYPE_ARRAY = /\[object Array\]/i,
TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i,
TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i,
TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i,
TYPE_IMAGE_DATA = /\[object ImageData\]/i,
UNDEFINED = 'undefined',
canvas = getCanvas(),
context = canvas.getContext('2d'),
previous = root[name],
imagediff, jasmine;
// Creation
function getCanvas (width, height) {
var
canvas = Canvas ?
new Canvas() :
document.createElement('canvas');
if (width) canvas.width = width;
if (height) canvas.height = height;
return canvas;
}
function getImageData (width, height) {
canvas.width = width;
canvas.height = height;
context.clearRect(0, 0, width, height);
return context.createImageData(width, height);
}
// expost canvas module
function getCanvasRef() {
return Canvas;
}
// Type Checking
function isImage (object) {
return isType(object, TYPE_IMAGE);
}
function isCanvas (object) {
return isType(object, TYPE_CANVAS);
}
function isContext (object) {
return isType(object, TYPE_CONTEXT);
}
function isImageData (object) {
return !!(object &&
isType(object, TYPE_IMAGE_DATA) &&
typeof(object.width) !== UNDEFINED &&
typeof(object.height) !== UNDEFINED &&
typeof(object.data) !== UNDEFINED);
}
function isImageType (object) {
return (
isImage(object) ||
isCanvas(object) ||
isContext(object) ||
isImageData(object)
);
}
function isType (object, type) {
return typeof (object) === 'object' && !!Object.prototype.toString.apply(object).match(type);
}
// Type Conversion
function copyImageData (imageData) {
var
height = imageData.height,
width = imageData.width,
data = imageData.data,
newImageData, newData, i;
canvas.width = width;
canvas.height = height;
newImageData = context.getImageData(0, 0, width, height);
newData = newImageData.data;
for (i = imageData.data.length; i--;) {
newData[i] = data[i];
}
return newImageData;
}
function toImageData (object) {
if (isImage(object)) { return toImageDataFromImage(object); }
if (isCanvas(object)) { return toImageDataFromCanvas(object); }
if (isContext(object)) { return toImageDataFromContext(object); }
if (isImageData(object)) { return object; }
}
function toImageDataFromImage (image) {
var
height = image.height,
width = image.width;
canvas.width = width;
canvas.height = height;
context.clearRect(0, 0, width, height);
context.drawImage(image, 0, 0);
return context.getImageData(0, 0, width, height);
}
function toImageDataFromCanvas (canvas) {
var
height = canvas.height,
width = canvas.width,
context = canvas.getContext('2d');
return context.getImageData(0, 0, width, height);
}
function toImageDataFromContext (context) {
var
canvas = context.canvas,
height = canvas.height,
width = canvas.width;
return context.getImageData(0, 0, width, height);
}
function toCanvas (object) {
var
data = toImageData(object),
canvas = getCanvas(data.width, data.height),
context = canvas.getContext('2d');
context.putImageData(data, 0, 0);
return canvas;
}
// ImageData Equality Operators
function equalWidth (a, b) {
return a.width === b.width;
}
function equalHeight (a, b) {
return a.height === b.height;
}
function equalDimensions (a, b) {
return equalHeight(a, b) && equalWidth(a, b);
}
function equal (a, b, tolerance) {
var
aData = a.data,
bData = b.data,
length = aData.length,
i;
tolerance = tolerance || 0;
if (!equalDimensions(a, b)) return false;
for (i = length; i--;) {
if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) {
var x = i % (a.width * 4);
var y = (i - x) / (a.width * 4);
var color = x % 4;
x = (x - color) / 4;
console.log("Difference x", x, "y", y, ["R", "G", "B", "A"][color], " - ", aData[i], " !== ", bData[i]);
return false;
}
}
return true;
}
// Diff
function diff (a, b, options) {
return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options);
}
function diffEqual (a, b, options) {
var
height = a.height,
width = a.width,
c = getImageData(width, height), // c = a - b
aData = a.data,
bData = b.data,
cData = c.data,
length = cData.length,
row, column,
i, j, k, v;
for (i = 0; i < length; i += 4) {
cData[i] = Math.abs(aData[i] - bData[i]);
cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
cData[i+3] = Math.abs(255 - Math.abs(aData[i+3] - bData[i+3]));
}
return c;
}
function diffUnequal (a, b, options) {
var
height = Math.max(a.height, b.height),
width = Math.max(a.width, b.width),
c = getImageData(width, height), // c = a - b
aData = a.data,
bData = b.data,
cData = c.data,
align = options && options.align,
rowOffset,
columnOffset,
row, column,
i, j, k, v;
for (i = cData.length - 1; i > 0; i = i - 4) {
cData[i] = 255;
}
// Add First Image
offsets(a);
for (row = a.height; row--;){
for (column = a.width; column--;) {
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
j = 4 * (row * a.width + column);
cData[i+0] = aData[j+0]; // r
cData[i+1] = aData[j+1]; // g
cData[i+2] = aData[j+2]; // b
// cData[i+3] = aData[j+3]; // a
}
}
// Subtract Second Image
offsets(b);
for (row = b.height; row--;){
for (column = b.width; column--;) {
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
j = 4 * (row * b.width + column);
cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
}
}
// Helpers
function offsets (imageData) {
if (align === 'top') {
rowOffset = 0;
columnOffset = 0;
} else {
rowOffset = Math.floor((height - imageData.height) / 2);
columnOffset = Math.floor((width - imageData.width) / 2);
}
}
return c;
}
// Validation
function checkType () {
var i;
for (i = 0; i < arguments.length; i++) {
if (!isImageType(arguments[i])) {
throw {
name : 'ImageTypeError',
message : 'Submitted object was not an image.'
};
}
}
}
// Jasmine Matchers
function get (element, content) {
element = document.createElement(element);
if (element && content) {
element.innerHTML = content;
}
return element;
}
jasmine = {
toBeImageData : function () {
return imagediff.isImageData(this.actual);
},
toImageDiffEqual : function (expected, tolerance) {
if (typeof (document) !== UNDEFINED) {
this.message = function () {
var
div = get('div'),
a = get('div', '<div>Actual:</div>'),
b = get('div', '<div>Expected:</div>'),
c = get('div', '<div>Diff:</div>'),
diff = imagediff.diff(this.actual, expected),
canvas = getCanvas(),
context;
canvas.height = diff.height;
canvas.width = diff.width;
div.style.overflow = 'hidden';
a.style.float = 'left';
b.style.float = 'left';
c.style.float = 'left';
context = canvas.getContext('2d');
context.putImageData(diff, 0, 0);
a.appendChild(toCanvas(this.actual));
b.appendChild(toCanvas(expected));
c.appendChild(canvas);
div.appendChild(a);
div.appendChild(b);
div.appendChild(c);
return [
div,
"Expected not to be equal."
];
};
}
return imagediff.equal(this.actual, expected, tolerance);
}
};
// Image Output
function imageDataToPNG (imageData, outputFile, callback) {
var
canvas = toCanvas(imageData),
base64Data,
decodedImage;
callback = callback || Function;
base64Data = canvas.toDataURL().replace(/^data:image\/\w+;base64,/,"");
decodedImage = Buffer.from(base64Data, 'base64');
require('fs').writeFile(outputFile, decodedImage, callback);
}
// Definition
imagediff = {
createCanvas : getCanvas,
createImageData : getImageData,
getCanvasRef: getCanvasRef,
isImage : isImage,
isCanvas : isCanvas,
isContext : isContext,
isImageData : isImageData,
isImageType : isImageType,
toImageData : function (object) {
checkType(object);
if (isImageData(object)) { return copyImageData(object); }
return toImageData(object);
},
equal : function (a, b, tolerance) {
checkType(a, b);
a = toImageData(a);
b = toImageData(b);
return equal(a, b, tolerance);
},
diff : function (a, b, options) {
checkType(a, b);
a = toImageData(a);
b = toImageData(b);
return diff(a, b, options);
},
jasmine : jasmine,
// Compatibility
noConflict : function () {
root[name] = previous;
return imagediff;
}
};
if (typeof module !== 'undefined') {
imagediff.imageDataToPNG = imageDataToPNG;
}
return imagediff;
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Some files were not shown because too many files have changed in this diff Show more