Compare commits
191 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31f3472b34 | ||
|
|
8eabb688fe | ||
|
|
c2302025b7 | ||
|
|
bdcc276351 | ||
|
|
f90b7c7121 | ||
|
|
738c9649b3 | ||
|
|
35258f8817 | ||
|
|
8d4eefe0b5 | ||
|
|
28a0c02502 | ||
|
|
e5f4fdef88 | ||
|
|
e530f459da | ||
|
|
6736cef032 | ||
|
|
c3a2ba2cc5 | ||
|
|
792e432c9c | ||
|
|
2c7b959a73 | ||
|
|
0704ce06d7 | ||
|
|
40178cabb2 | ||
|
|
f8cb8eecbc | ||
|
|
8cd8a5549b | ||
|
|
088dbb7b8a | ||
|
|
f3110ce2e0 | ||
|
|
b127fc658d | ||
|
|
7158967dcf | ||
|
|
68b6da32b6 | ||
|
|
6b93d6f9cc | ||
|
|
5e92cc3722 | ||
|
|
32271b56e3 | ||
|
|
31bedc7bb0 | ||
|
|
5d5f543c41 | ||
|
|
90e00c07fd | ||
|
|
257a4c972d | ||
|
|
8cf115b010 | ||
|
|
9e499c4f92 | ||
|
|
7e160967c9 | ||
|
|
d6bf8d133a | ||
|
|
0b5822a4be | ||
|
|
728cc7e265 | ||
|
|
7acfa680b1 | ||
|
|
decc76f686 | ||
|
|
2d78949365 | ||
|
|
ae7b0a1534 | ||
|
|
0a6201df87 | ||
|
|
32d0580228 | ||
|
|
f947e3fcfb | ||
|
|
f5acfc5855 | ||
|
|
a3d9c92eec | ||
|
|
bdc384e9c3 | ||
|
|
08534acc84 | ||
|
|
2c37ebcbf2 | ||
|
|
a1d9b0a52d | ||
|
|
9c35587467 | ||
|
|
f28d2a9e02 | ||
|
|
4c09644993 | ||
|
|
70f55344de | ||
|
|
8bb2686bb9 | ||
|
|
d64454721c | ||
|
|
2c5f972566 | ||
|
|
e3bef7d1a6 | ||
|
|
3ad37374e3 | ||
|
|
b495e60323 | ||
|
|
96c67ea08d | ||
|
|
f49e99aa28 | ||
|
|
e5b4faf258 | ||
|
|
b914ab4d80 | ||
|
|
ebdb3c546c | ||
|
|
e1c1022162 | ||
|
|
3c121291e8 | ||
|
|
e406a7527f | ||
|
|
d1f3b0b9b1 | ||
|
|
8e20083602 | ||
|
|
c7d7cbc305 | ||
|
|
4d6b6c16da | ||
|
|
a3f3ef50df | ||
|
|
f5faba0119 | ||
|
|
785d6c1ab0 | ||
|
|
ff2d83c6d5 | ||
|
|
b23bc321a6 | ||
|
|
596f130230 | ||
|
|
2ec7f0d50f | ||
|
|
1ba93c2af8 | ||
|
|
5ee7782212 | ||
|
|
c76df92fd4 | ||
|
|
bc271fcf38 | ||
|
|
ac815b87ee | ||
|
|
42fd49f0b6 | ||
|
|
a04ccc330a | ||
|
|
214838df84 | ||
|
|
db33e31a41 | ||
|
|
43852cdab6 | ||
|
|
346bdaa07b | ||
|
|
f4b394e117 | ||
|
|
e81a7525e5 | ||
|
|
76450f7e2d | ||
|
|
717cbb13b4 | ||
|
|
8575154813 | ||
|
|
45033d4031 | ||
|
|
912df207b5 | ||
|
|
0da750762f | ||
|
|
7878c860ad | ||
|
|
c631e6507c | ||
|
|
b5105432b3 | ||
|
|
ace6fa505b | ||
|
|
4004ac2cd5 | ||
|
|
7717852dad | ||
|
|
3c3eb6d709 | ||
|
|
b90e811243 | ||
|
|
d7a8638b72 | ||
|
|
d08fc30edb | ||
|
|
159221c115 | ||
|
|
780a796de3 | ||
|
|
4428bba483 | ||
|
|
6467ad747b | ||
|
|
83d60edeaf | ||
|
|
75d2927e75 | ||
|
|
99c5b43871 | ||
|
|
79409fb535 | ||
|
|
a0596a58c4 | ||
|
|
7f456bacda | ||
|
|
15896644fd | ||
|
|
33be98c413 | ||
|
|
ba44715818 | ||
|
|
880d1b0814 | ||
|
|
109c83eba2 | ||
|
|
dd5003a518 | ||
|
|
6cf6d787bc | ||
|
|
3ccd332f5a | ||
|
|
3ddd3f11a3 | ||
|
|
adf5216a49 | ||
|
|
6607d71c04 | ||
|
|
b23964c551 | ||
|
|
88869809e5 | ||
|
|
119d403098 | ||
|
|
2fbf81e598 | ||
|
|
735902ff9e | ||
|
|
1c817dbb9d | ||
|
|
79465895af | ||
|
|
b24153494e | ||
|
|
411d777e22 | ||
|
|
a5b282356b | ||
|
|
5310b0ed5f | ||
|
|
288a696584 | ||
|
|
cdd6f0c5ab | ||
|
|
f84416a523 | ||
|
|
71a371d6cd | ||
|
|
c450597b09 | ||
|
|
ff9d322a36 | ||
|
|
eabc8ac061 | ||
|
|
8cf14b9ca2 | ||
|
|
41c5a14950 | ||
|
|
99eae1c711 | ||
|
|
bf5ae3c54e | ||
|
|
8155d300f0 | ||
|
|
2398b72c8f | ||
|
|
1183e25413 | ||
|
|
53bd460f4f | ||
|
|
d6b6f66bf4 | ||
|
|
a3620b0f02 | ||
|
|
3a3b1ef242 | ||
|
|
7c6f744795 | ||
|
|
47c9d09276 | ||
|
|
ced0e4c2e0 | ||
|
|
132841b3d3 | ||
|
|
e54257c4bd | ||
|
|
2291c1816d | ||
|
|
ee3dc328c5 | ||
|
|
a5b996a9ef | ||
|
|
2914659815 | ||
|
|
b89ef0384f | ||
|
|
233925190c | ||
|
|
8933a9048d | ||
|
|
cb346c5685 | ||
|
|
80addf080b | ||
|
|
b9293eaa2b | ||
|
|
51365543ae | ||
|
|
86a114be2f | ||
|
|
d9452afc44 | ||
|
|
9afd1b2358 | ||
|
|
4def219e1e | ||
|
|
8c8f205fa4 | ||
|
|
f7c70a92b4 | ||
|
|
2d91824d0c | ||
|
|
d601f60c79 | ||
|
|
807ffcd66f | ||
|
|
b5328d3574 | ||
|
|
30e6e38e22 | ||
|
|
fa9693a42c | ||
|
|
4c508c661e | ||
|
|
abc6f7e789 | ||
|
|
dbe7fbe390 | ||
|
|
c38a7edf70 | ||
|
|
51cc500a2e |
2
.eslintignore
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/browser.js
|
||||||
|
/test/imagediff.js
|
||||||
19
.eslintrc.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"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
|
|
@ -1 +1,6 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
.idea
|
||||||
|
coverage
|
||||||
|
examples/*.png
|
||||||
|
browser\.js
|
||||||
|
.nyc_output
|
||||||
|
|
|
||||||
5
.npmignore
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
test
|
||||||
|
examples
|
||||||
|
.travis.yml
|
||||||
|
appveyor.yml
|
||||||
|
|
||||||
3
.prettierignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
/browser.js
|
||||||
|
/test/imagediff.js
|
||||||
|
.nyc_output
|
||||||
9
.travis.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
language: node_js
|
||||||
|
after_success:
|
||||||
|
- if [ "$TRAVIS_NODE_VERSION" = "10" ]; then yarn coverage && yarn codecov; fi
|
||||||
|
node_js:
|
||||||
|
- "10"
|
||||||
|
- "12"
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
3
LICENSE
|
|
@ -1,4 +1,5 @@
|
||||||
Copyright (c) 2012 Kuba Niegowski
|
pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors
|
||||||
|
pngjs derived work Copyright (c) 2012 Kuba Niegowski
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
399
README.md
|
|
@ -1,150 +1,411 @@
|
||||||
About
|
[](https://travis-ci.com/lukeapage/pngjs) [](https://ci.appveyor.com/project/lukeapage/pngjs/branch/master) [](https://codecov.io/gh/lukeapage/pngjs) [](http://badge.fury.io/js/pngjs)
|
||||||
========
|
|
||||||
Simple PNG encoder/decoder for Node.js with no native dependencies.
|
# 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
|
||||||
|
|
||||||
Installation
|
|
||||||
===============
|
|
||||||
```
|
```
|
||||||
$ npm install node-png
|
$ npm install pngjs --save
|
||||||
```
|
```
|
||||||
|
|
||||||
Example
|
# 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
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var fs = require('fs'),
|
var fs = require("fs"),
|
||||||
PNG = require('pngjs').PNG;
|
PNG = require("pngjs").PNG;
|
||||||
|
|
||||||
fs.createReadStream('in.png')
|
fs.createReadStream("in.png")
|
||||||
.pipe(new PNG({
|
.pipe(
|
||||||
filterType: 4
|
new PNG({
|
||||||
}))
|
filterType: 4,
|
||||||
.on('parsed', function() {
|
})
|
||||||
|
)
|
||||||
|
.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;
|
||||||
|
|
||||||
for (var y = 0; y < this.height; y++) {
|
// invert color
|
||||||
for (var x = 0; x < this.width; x++) {
|
this.data[idx] = 255 - this.data[idx];
|
||||||
var idx = (this.width * y + x) << 2;
|
this.data[idx + 1] = 255 - this.data[idx + 1];
|
||||||
|
this.data[idx + 2] = 255 - this.data[idx + 2];
|
||||||
|
|
||||||
// invert color
|
// and reduce opacity
|
||||||
this.data[idx] = 255 - this.data[idx];
|
this.data[idx + 3] = this.data[idx + 3] >> 1;
|
||||||
this.data[idx+1] = 255 - this.data[idx+1];
|
}
|
||||||
this.data[idx+2] = 255 - this.data[idx+2];
|
}
|
||||||
|
|
||||||
// and reduce opacity
|
this.pack().pipe(fs.createWriteStream("out.png"));
|
||||||
this.data[idx+3] = this.data[idx+3] >> 1;
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pack().pipe(fs.createWriteStream('out.png'));
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For more examples see `examples` folder.
|
For more examples see `examples` folder.
|
||||||
|
|
||||||
Documentation
|
# Async API
|
||||||
================
|
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
### Supported ancillary chunks
|
|
||||||
- `gAMA` - gamma,
|
|
||||||
- `tRNS` - transparency (but only for paletted image)
|
|
||||||
|
|
||||||
|
|
||||||
## Class: PNG
|
## Class: PNG
|
||||||
|
|
||||||
`PNG` is readable and writable `Stream`.
|
`PNG` is readable and writable `Stream`.
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
- `width` - use this with `height` if you want to create png from scratch
|
- `width` - use this with `height` if you want to create png from scratch
|
||||||
- `height` - as above
|
- `height` - as above
|
||||||
- `checkCRC` - whether parser should be strict about checksums in source stream (default: `true`)
|
- `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)
|
- `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)
|
- `deflateLevel` - compression level for deflate (default: 9)
|
||||||
- `deflateStrategy` - compression strategy for delate (default: 3)
|
- `deflateStrategy` - compression strategy for deflate (default: 3)
|
||||||
|
- `deflateFactory` - deflate stream factory (default: `zlib.createDeflate`)
|
||||||
- `filterType` - png filtering method for scanlines (default: -1 => auto, accepts array of numbers 0-4)
|
- `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"
|
### Event "metadata"
|
||||||
|
|
||||||
`function(metadata) { }`
|
`function(metadata) { }`
|
||||||
Image's header has been parsed, metadata contains this information:
|
Image's header has been parsed, metadata contains this information:
|
||||||
|
|
||||||
- `width` image size in pixels
|
- `width` image size in pixels
|
||||||
- `height` image size in pixels
|
- `height` image size in pixels
|
||||||
- `palette` image is paletted
|
- `palette` image is paletted
|
||||||
- `color` image is not grayscale
|
- `color` image is not grayscale
|
||||||
- `alpha` image contains alpha channel
|
- `alpha` image contains alpha channel
|
||||||
|
- `interlace` image is interlaced
|
||||||
|
|
||||||
### Event: "parsed"
|
### Event: "parsed"
|
||||||
`function(data) { }`
|
|
||||||
Input image has been completly parsed, `data` is complete and ready for modification.
|
|
||||||
|
|
||||||
|
`function(data) { }`
|
||||||
|
Input image has been completely parsed, `data` is complete and ready for modification.
|
||||||
|
|
||||||
### Event: "error"
|
### Event: "error"
|
||||||
|
|
||||||
`function(error) { }`
|
`function(error) { }`
|
||||||
|
|
||||||
|
|
||||||
### png.parse(data, [callback])
|
### png.parse(data, [callback])
|
||||||
Parses PNG file data. Alternatively you can stream data to instance of PNG.
|
|
||||||
|
Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG.
|
||||||
|
|
||||||
Optional `callback` is once called on `error` or `parsed`. The callback gets
|
Optional `callback` is once called on `error` or `parsed`. The callback gets
|
||||||
two arguments `(err, data)`.
|
two arguments `(err, data)`.
|
||||||
|
|
||||||
Returns `this` for method chaining.
|
Returns `this` for method chaining.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
new PNG({ filterType: 4 }).parse(imageData, function (error, data) {
|
||||||
|
console.log(error, data);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### png.pack()
|
### png.pack()
|
||||||
|
|
||||||
Starts converting data to PNG file Stream.
|
Starts converting data to PNG file Stream.
|
||||||
|
|
||||||
Returns `this` for method chaining.
|
Returns `this` for method chaining.
|
||||||
|
|
||||||
|
|
||||||
### png.bitblt(dst, sx, sy, w, h, 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`).
|
|
||||||
|
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`).
|
||||||
|
|
||||||
Returns `this` for method chaining.
|
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
|
### Property: width
|
||||||
|
|
||||||
Width of image in pixels
|
Width of image in pixels
|
||||||
|
|
||||||
|
|
||||||
### Property: height
|
### Property: height
|
||||||
|
|
||||||
Height of image in pixels
|
Height of image in pixels
|
||||||
|
|
||||||
|
|
||||||
### Property: data
|
### Property: data
|
||||||
|
|
||||||
Buffer of image pixel data. Every pixel consists 4 bytes: R, G, B, A (opacity).
|
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)
|
Gamma of image (0 if not specified)
|
||||||
|
|
||||||
Changelog
|
## Packing a PNG and removing alpha (RGBA to RGB)
|
||||||
============
|
|
||||||
|
|
||||||
### 0.4.0 - Jun 05 2013
|
When removing the alpha channel from an image, there needs to be a background color to correctly
|
||||||
- fixed reading of destroyed input stream
|
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-alpha - 29 Nov 2012
|
```js
|
||||||
- added zlib deflateStrategy option, default to Z_RLE (by pdpotter)
|
var fs = require("fs"),
|
||||||
- added possibility to use multiple filters (by pdpotter, modified by niegowski)
|
PNG = require("pngjs").PNG;
|
||||||
|
|
||||||
### 0.3.0-alpha - 23 Aug 2012
|
fs.createReadStream("in.png")
|
||||||
- Processing data as Streams, not complete Buffers of data
|
.pipe(
|
||||||
|
new PNG({
|
||||||
|
colorType: 2,
|
||||||
|
bgColor: {
|
||||||
|
red: 0,
|
||||||
|
green: 255,
|
||||||
|
blue: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.on("parsed", function () {
|
||||||
|
this.pack().pipe(fs.createWriteStream("out.png"));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### 0.2.0-alpha - 21 Aug 2012
|
# Sync API
|
||||||
- Input added palette, grayscale, no alpha support
|
|
||||||
- Better scanline filter selection
|
|
||||||
|
|
||||||
### 0.1.0-alpha - 19 Aug 2012
|
## PNG.sync
|
||||||
- First version
|
|
||||||
|
|
||||||
License
|
### 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
|
||||||
|
|
||||||
(The MIT License)
|
(The MIT License)
|
||||||
|
|
||||||
Copyright (c) 2012 Kuba Niegowski
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
|
|
|
||||||
28
appveyor.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
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
|
||||||
50
examples/16bit_write.js
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/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"));
|
||||||
|
|
@ -1,26 +1,22 @@
|
||||||
|
let fs = require("fs"),
|
||||||
|
PNG = require("../lib/png").PNG; // note require('pngjs') outside this project
|
||||||
|
|
||||||
var fs = require('fs'),
|
fs.createReadStream("test/in/basi0g01.png")
|
||||||
PNG = require('pngjs').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;
|
||||||
|
|
||||||
fs.createReadStream('in.png')
|
// invert color
|
||||||
.pipe(new PNG({
|
this.data[idx] = 255 - this.data[idx];
|
||||||
filterType: 4
|
this.data[idx + 1] = 255 - this.data[idx + 1];
|
||||||
}))
|
this.data[idx + 2] = 255 - this.data[idx + 2];
|
||||||
.on('parsed', function() {
|
|
||||||
|
|
||||||
for (var y = 0; y < this.height; y++) {
|
// and reduce opacity
|
||||||
for (var x = 0; x < this.width; x++) {
|
this.data[idx + 3] = this.data[idx + 3] >> 1;
|
||||||
var idx = (this.width * y + x) << 2;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// invert color
|
this.pack().pipe(fs.createWriteStream("out.png"));
|
||||||
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'));
|
|
||||||
});
|
|
||||||
|
|
|
||||||
25
examples/newfile.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
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!");
|
||||||
|
});
|
||||||
34
examples/set-colortype.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
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"));
|
||||||
|
|
@ -1,30 +1,28 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var fs = require('fs'),
|
let fs = require("fs"),
|
||||||
PNG = require('../lib/png').PNG;
|
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");
|
||||||
|
|
||||||
var png = new PNG({
|
png.on("parsed", function () {
|
||||||
filterType: -1
|
for (let y = 0; y < png.height; y++) {
|
||||||
}),
|
for (let x = 0; x < png.width; x++) {
|
||||||
src = fs.createReadStream(process.argv[2]),
|
let idx = (png.width * y + x) << 2;
|
||||||
dst = fs.createWriteStream(process.argv[3] || 'out.png');
|
|
||||||
|
|
||||||
|
if (
|
||||||
png.on('parsed', function() {
|
Math.abs(png.data[idx] - png.data[idx + 1]) <= 1 &&
|
||||||
|
Math.abs(png.data[idx + 1] - png.data[idx + 2]) <= 1
|
||||||
for (var y = 0; y < png.height; y++) {
|
)
|
||||||
for (var x = 0; x < png.width; x++) {
|
png.data[idx] = png.data[idx + 1] = png.data[idx + 2];
|
||||||
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);
|
src.pipe(png);
|
||||||
|
|
|
||||||
18
examples/sync.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/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);
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
#!/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'));
|
|
||||||
|
Before Width: | Height: | Size: 93 B |
|
|
@ -1,38 +0,0 @@
|
||||||
<!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>
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
265
lib/bitmapper.js
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
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;
|
||||||
|
};
|
||||||
156
lib/bitpacker.js
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
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
Executable file → Normal file
|
|
@ -1,200 +1,190 @@
|
||||||
// Copyright (c) 2012 Kuba Niegowski
|
let util = require("util");
|
||||||
//
|
let Stream = require("stream");
|
||||||
// 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';
|
let ChunkStream = function () {
|
||||||
|
Stream.call(this);
|
||||||
|
|
||||||
|
this._buffers = [];
|
||||||
|
this._buffered = 0;
|
||||||
|
|
||||||
var util = require('util'),
|
this._reads = [];
|
||||||
Stream = require('stream');
|
this._paused = false;
|
||||||
|
|
||||||
|
this._encoding = "utf8";
|
||||||
var ChunkStream = module.exports = function() {
|
this.writable = true;
|
||||||
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);
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
ChunkStream.prototype.read = function(length, callback) {
|
process.nextTick(
|
||||||
|
function () {
|
||||||
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();
|
this._process();
|
||||||
|
|
||||||
// its paused and there is not enought data then ask for more
|
// its paused and there is not enought data then ask for more
|
||||||
if (this._paused && this._reads.length > 0) {
|
if (this._paused && this._reads && this._reads.length > 0) {
|
||||||
this._paused = false;
|
this._paused = false;
|
||||||
|
|
||||||
this.emit('drain');
|
this.emit("drain");
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ChunkStream.prototype.write = function(data, encoding) {
|
ChunkStream.prototype.write = function (data, encoding) {
|
||||||
|
if (!this.writable) {
|
||||||
|
this.emit("error", new Error("Stream not writable"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.writable) {
|
let dataBuffer;
|
||||||
this.emit('error', new Error('Stream not writable'));
|
if (Buffer.isBuffer(data)) {
|
||||||
return false;
|
dataBuffer = data;
|
||||||
}
|
} else {
|
||||||
|
dataBuffer = Buffer.from(data, encoding || this._encoding);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Buffer.isBuffer(data))
|
this._buffers.push(dataBuffer);
|
||||||
data = new Buffer(data, encoding || this._encoding);
|
this._buffered += dataBuffer.length;
|
||||||
|
|
||||||
this._buffers.push(data);
|
this._process();
|
||||||
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();
|
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.destroySoon = ChunkStream.prototype.end;
|
||||||
|
|
||||||
ChunkStream.prototype._end = function() {
|
ChunkStream.prototype._end = function () {
|
||||||
|
if (this._reads.length > 0) {
|
||||||
|
this.emit("error", new Error("Unexpected end of input"));
|
||||||
|
}
|
||||||
|
|
||||||
if (this._reads.length > 0) {
|
this.destroy();
|
||||||
this.emit('error',
|
};
|
||||||
new Error('There are some read requests waitng on finished stream')
|
|
||||||
);
|
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);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.destroy();
|
// remove all used buffers
|
||||||
|
if (count > 0) {
|
||||||
|
this._buffers.splice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._buffered -= read.length;
|
||||||
|
|
||||||
|
read.func.call(this, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
ChunkStream.prototype.destroy = function() {
|
ChunkStream.prototype._process = function () {
|
||||||
|
try {
|
||||||
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
|
// as long as there is any data and read requests
|
||||||
while (this._buffered > 0 && this._reads && this._reads.length > 0) {
|
while (this._buffered > 0 && this._reads && this._reads.length > 0) {
|
||||||
|
let read = this._reads[0];
|
||||||
|
|
||||||
var 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
|
||||||
|
|
||||||
// read any data (but no more than length)
|
this._processRead(read);
|
||||||
if (read.allowLess) {
|
} else {
|
||||||
|
// not enought data to satisfy first request in queue
|
||||||
// ok there is any data so that we can satisfy this request
|
// so we need to wait for more
|
||||||
this._reads.shift(); // == read
|
break;
|
||||||
|
}
|
||||||
// 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._buffers.length > 0 && this._buffers[0] == null) {
|
if (this._buffers && !this.writable) {
|
||||||
this._end();
|
this._end();
|
||||||
}
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
this.emit("error", ex);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
56
lib/constants.js
Executable file → Normal file
|
|
@ -1,38 +1,30 @@
|
||||||
// Copyright (c) 2012 Kuba Niegowski
|
export default {
|
||||||
//
|
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
|
||||||
// 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';
|
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
|
||||||
|
|
||||||
|
// color-type bits
|
||||||
|
COLORTYPE_GRAYSCALE: 0,
|
||||||
|
COLORTYPE_PALETTE: 1,
|
||||||
|
COLORTYPE_COLOR: 2,
|
||||||
|
COLORTYPE_ALPHA: 4, // e.g. grayscale and alpha
|
||||||
|
|
||||||
module.exports = {
|
// color-type combinations
|
||||||
|
COLORTYPE_PALETTE_COLOR: 3,
|
||||||
|
COLORTYPE_COLOR_ALPHA: 6,
|
||||||
|
|
||||||
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
|
COLORTYPE_TO_BPP_MAP: {
|
||||||
|
0: 1,
|
||||||
|
2: 3,
|
||||||
|
3: 1,
|
||||||
|
4: 2,
|
||||||
|
6: 4,
|
||||||
|
},
|
||||||
|
|
||||||
TYPE_IHDR: 0x49484452,
|
GAMMA_DIVISION: 100000,
|
||||||
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
Executable file → Normal file
|
|
@ -1,79 +1,40 @@
|
||||||
// Copyright (c) 2012 Kuba Niegowski
|
let crcTable = [];
|
||||||
//
|
|
||||||
// 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';
|
(function () {
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
var util = require('util'),
|
let currentCrc = i;
|
||||||
Stream = require('stream');
|
for (let j = 0; j < 8; j++) {
|
||||||
|
if (currentCrc & 1) {
|
||||||
|
currentCrc = 0xedb88320 ^ (currentCrc >>> 1);
|
||||||
var CrcStream = module.exports = function() {
|
} else {
|
||||||
Stream.call(this);
|
currentCrc = currentCrc >>> 1;
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return true;
|
crcTable[i] = currentCrc;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
let CrcCalculator = function () {
|
||||||
|
this._crc = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
CrcStream.prototype.end = function(data) {
|
export default CrcCalculator;
|
||||||
if (data) this.write(data);
|
|
||||||
|
|
||||||
this.emit('crc', this.crc32());
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
CrcStream.prototype.crc32 = function() {
|
CrcCalculator.prototype.crc32 = function () {
|
||||||
return this._crc ^ -1;
|
return this._crc ^ -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CrcCalculator.crc32 = function (buf) {
|
||||||
CrcStream.crc32 = function(buf) {
|
let crc = -1;
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
var crc = -1;
|
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
||||||
for (var i = 0; i < buf.length; i++) {
|
}
|
||||||
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
return crc ^ -1;
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
169
lib/filter-pack.js
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
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;
|
||||||
|
};
|
||||||
24
lib/filter-parse-async.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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;
|
||||||
19
lib/filter-parse-sync.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
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);
|
||||||
|
};
|
||||||
177
lib/filter-parse.js
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
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
|
|
@ -1,314 +0,0 @@
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
91
lib/format-normaliser.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
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;
|
||||||
|
};
|
||||||
98
lib/interlace.js
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
// 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
|
||||||
|
}
|
||||||
51
lib/packer-async.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
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);
|
||||||
|
};
|
||||||
52
lib/packer-sync.js
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
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
Executable file → Normal file
|
|
@ -1,110 +1,130 @@
|
||||||
// Copyright (c) 2012 Kuba Niegowski
|
import constants from "./constants.js";
|
||||||
//
|
import CrcStream from "./crc.js";
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
import bitPacker from "./bitpacker.js";
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
import filter from "./filter-pack.js";
|
||||||
// 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';
|
let zlib = require("zlib");
|
||||||
|
|
||||||
|
let Packer = function (options) {
|
||||||
|
this._options = options;
|
||||||
|
|
||||||
var util = require('util'),
|
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
|
||||||
Stream = require('stream'),
|
options.deflateLevel =
|
||||||
zlib = require('zlib'),
|
options.deflateLevel != null ? options.deflateLevel : 9;
|
||||||
Filter = require('./filter'),
|
options.deflateStrategy =
|
||||||
CrcStream = require('./crc'),
|
options.deflateStrategy != null ? options.deflateStrategy : 3;
|
||||||
constants = require('./constants');
|
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;
|
||||||
|
|
||||||
|
if (
|
||||||
var Packer = module.exports = function(options) {
|
[
|
||||||
Stream.call(this);
|
constants.COLORTYPE_GRAYSCALE,
|
||||||
|
constants.COLORTYPE_COLOR,
|
||||||
this._options = options;
|
constants.COLORTYPE_COLOR_ALPHA,
|
||||||
|
constants.COLORTYPE_ALPHA,
|
||||||
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
|
].indexOf(options.colorType) === -1
|
||||||
options.deflateLevel = options.deflateLevel || 9;
|
) {
|
||||||
options.deflateStrategy = options.deflateStrategy || 3;
|
throw new Error(
|
||||||
|
"option color type:" + options.colorType + " is not supported at present"
|
||||||
this.readable = true;
|
);
|
||||||
};
|
}
|
||||||
util.inherits(Packer, Stream);
|
if (
|
||||||
|
[
|
||||||
|
constants.COLORTYPE_GRAYSCALE,
|
||||||
Packer.prototype.pack = function(data, width, height) {
|
constants.COLORTYPE_COLOR,
|
||||||
|
constants.COLORTYPE_COLOR_ALPHA,
|
||||||
// Signature
|
constants.COLORTYPE_ALPHA,
|
||||||
this.emit('data', new Buffer(constants.PNG_SIGNATURE));
|
].indexOf(options.inputColorType) === -1
|
||||||
this.emit('data', this._packIHDR(width, height));
|
) {
|
||||||
|
throw new Error(
|
||||||
// filter pixel data
|
"option input color type:" +
|
||||||
var filter = new Filter(width, height, 4, data, this._options);
|
options.inputColorType +
|
||||||
var data = filter.filter();
|
" is not supported at present"
|
||||||
|
);
|
||||||
// compress it
|
}
|
||||||
var deflate = zlib.createDeflate({
|
if (options.bitDepth !== 8 && options.bitDepth !== 16) {
|
||||||
chunkSize: this._options.deflateChunkSize,
|
throw new Error(
|
||||||
level: this._options.deflateLevel,
|
"option bit depth:" + options.bitDepth + " is not supported at present"
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Packer.prototype._packChunk = function(type, data) {
|
export default Packer;
|
||||||
|
|
||||||
var len = (data ? data.length : 0),
|
Packer.prototype.getDeflateOptions = function () {
|
||||||
buf = new Buffer(len + 12);
|
return {
|
||||||
|
chunkSize: this._options.deflateChunkSize,
|
||||||
buf.writeUInt32BE(len, 0);
|
level: this._options.deflateLevel,
|
||||||
buf.writeUInt32BE(type, 4);
|
strategy: this._options.deflateStrategy,
|
||||||
|
};
|
||||||
if (data) data.copy(buf, 8);
|
|
||||||
|
|
||||||
buf.writeInt32BE(CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4);
|
|
||||||
return buf;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Packer.prototype._packIHDR = function(width, height) {
|
Packer.prototype.createDeflate = function () {
|
||||||
|
return this._options.deflateFactory(this.getDeflateOptions());
|
||||||
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._packIDAT = function(data) {
|
Packer.prototype.filterData = function (data, width, height) {
|
||||||
return this._packChunk(constants.TYPE_IDAT, data);
|
// 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._packIEND = function() {
|
Packer.prototype._packChunk = function (type, data) {
|
||||||
return this._packChunk(constants.TYPE_IEND, null);
|
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);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
14
lib/paeth-predictor.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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;
|
||||||
|
};
|
||||||
166
lib/parser-async.js
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
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);
|
||||||
|
};
|
||||||
107
lib/parser-sync.js
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
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;
|
||||||
|
};
|
||||||
565
lib/parser.js
Executable file → Normal file
|
|
@ -1,359 +1,290 @@
|
||||||
// Copyright (c) 2012 Kuba Niegowski
|
"use strict";
|
||||||
//
|
|
||||||
// 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';
|
let constants = require("./constants");
|
||||||
|
let CrcCalculator = require("./crc");
|
||||||
|
|
||||||
|
let Parser = (module.exports = function (options, dependencies) {
|
||||||
|
this._options = options;
|
||||||
|
options.checkCRC = options.checkCRC !== false;
|
||||||
|
|
||||||
var util = require('util'),
|
this._hasIHDR = false;
|
||||||
zlib = require('zlib'),
|
this._hasIEND = false;
|
||||||
CrcStream = require('./crc'),
|
this._emittedHeadersFinished = false;
|
||||||
ChunkStream = require('./chunkstream'),
|
|
||||||
constants = require('./constants'),
|
|
||||||
Filter = require('./filter');
|
|
||||||
|
|
||||||
|
// input flags/metadata
|
||||||
|
this._palette = [];
|
||||||
|
this._colorType = 0;
|
||||||
|
|
||||||
var Parser = module.exports = function(options) {
|
this._chunks = {};
|
||||||
ChunkStream.call(this);
|
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._options = options;
|
this.read = dependencies.read;
|
||||||
options.checkCRC = options.checkCRC !== false;
|
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._hasIHDR = false;
|
Parser.prototype.start = function () {
|
||||||
this._hasIEND = false;
|
this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
|
||||||
|
|
||||||
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._handleSignature = function() {
|
Parser.prototype._parseSignature = function (data) {
|
||||||
this.read(constants.PNG_SIGNATURE.length,
|
let signature = constants.PNG_SIGNATURE;
|
||||||
this._parseSignature.bind(this)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Parser.prototype._parseSignature = function(data) {
|
for (let i = 0; i < signature.length; i++) {
|
||||||
|
if (data[i] !== signature[i]) {
|
||||||
var signature = constants.PNG_SIGNATURE;
|
this.error(new Error("Invalid file signature"));
|
||||||
|
return;
|
||||||
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));
|
this.read(8, this._parseChunkBegin.bind(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype._parseChunkBegin = function(data) {
|
Parser.prototype._handleIHDR = function (length) {
|
||||||
|
this.read(length, this._parseIHDR.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseIHDR = function (data) {
|
||||||
|
this._crc.write(data);
|
||||||
|
|
||||||
// chunk content length
|
let width = data.readUInt32BE(0);
|
||||||
var length = 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 type
|
// console.log(' width', width, 'height', height,
|
||||||
var type = data.readUInt32BE(4),
|
// 'depth', depth, 'colorType', colorType,
|
||||||
name = '';
|
// 'compr', compr, 'filter', filter, 'interlace', interlace
|
||||||
for (var i = 4; i < 8; i++)
|
// );
|
||||||
name += String.fromCharCode(data[i]);
|
|
||||||
|
|
||||||
// console.log('chunk ', name, length);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// chunk flags
|
this._colorType = colorType;
|
||||||
var ancillary = !!(data[4] & 0x20), // or critical
|
|
||||||
priv = !!(data[5] & 0x20), // or public
|
|
||||||
safeToCopy = !!(data[7] & 0x20); // or unsafe
|
|
||||||
|
|
||||||
if (!this._hasIHDR && type != constants.TYPE_IHDR) {
|
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
|
||||||
this.emit('error', new Error('Expected IHDR on beggining'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._crc = new CrcStream();
|
this._hasIHDR = true;
|
||||||
this._crc.write(new Buffer(name));
|
|
||||||
|
|
||||||
if (this._chunks[type]) {
|
this.metadata({
|
||||||
return this._chunks[type](length);
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
} else if (!ancillary) {
|
this._handleChunkEnd();
|
||||||
this.emit('error', new Error('Unsupported critical chunk type ' + name));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.read(length + 4, this._skipChunk.bind(this));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype._skipChunk = function(data) {
|
Parser.prototype._handlePLTE = function (length) {
|
||||||
this.read(8, this._parseChunkBegin.bind(this));
|
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._handleChunkEnd = function() {
|
Parser.prototype._handleTRNS = function (length) {
|
||||||
this.read(4, this._parseChunkEnd.bind(this));
|
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 (data.length > this._palette.length) {
|
||||||
|
this.error(new Error("More transparent colors than palette size"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
this._palette[i][3] = data[i];
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype._parseChunkEnd = function(data) {
|
Parser.prototype._handleGAMA = function (length) {
|
||||||
|
this.read(length, this._parseGAMA.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseGAMA = function (data) {
|
||||||
|
this._crc.write(data);
|
||||||
|
this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);
|
||||||
|
|
||||||
var fileCrc = data.readInt32BE(0),
|
this._handleChunkEnd();
|
||||||
calcCrc = this._crc.crc32();
|
|
||||||
|
|
||||||
// check CRC
|
|
||||||
if (this._options.checkCRC && calcCrc != fileCrc) {
|
|
||||||
this.emit('error', new Error('Crc error'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._hasIEND) {
|
|
||||||
this.destroySoon();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.read(8, this._parseChunkBegin.bind(this));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleIDAT = function (length) {
|
||||||
Parser.prototype._handleIHDR = function(length) {
|
if (!this._emittedHeadersFinished) {
|
||||||
this.read(length, this._parseIHDR.bind(this));
|
this._emittedHeadersFinished = true;
|
||||||
|
this.headersFinished();
|
||||||
|
}
|
||||||
|
this.read(-length, this._parseIDAT.bind(this, length));
|
||||||
};
|
};
|
||||||
Parser.prototype._parseIHDR = function(data) {
|
Parser.prototype._parseIDAT = function (length, data) {
|
||||||
|
this._crc.write(data);
|
||||||
|
|
||||||
this._crc.write(data);
|
if (
|
||||||
|
this._colorType === constants.COLORTYPE_PALETTE_COLOR &&
|
||||||
|
this._palette.length === 0
|
||||||
|
) {
|
||||||
|
throw new Error("Expected palette not found");
|
||||||
|
}
|
||||||
|
|
||||||
var width = data.readUInt32BE(0),
|
this.inflateData(data);
|
||||||
height = data.readUInt32BE(4),
|
let leftOverLength = length - data.length;
|
||||||
depth = data[8],
|
|
||||||
colorType = data[9], // bits: 1 palette, 2 color, 4 alpha
|
|
||||||
compr = data[10],
|
|
||||||
filter = data[11],
|
|
||||||
interlace = data[12];
|
|
||||||
|
|
||||||
// console.log(' width', width, 'height', height,
|
|
||||||
// 'depth', depth, 'colorType', colorType,
|
|
||||||
// 'compr', compr, 'filter', filter, 'interlace', interlace
|
|
||||||
// );
|
|
||||||
|
|
||||||
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();
|
this._handleChunkEnd();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleIEND = function (length) {
|
||||||
Parser.prototype._handlePLTE = function(length) {
|
this.read(length, this._parseIEND.bind(this));
|
||||||
this.read(length, this._parsePLTE.bind(this));
|
|
||||||
};
|
};
|
||||||
Parser.prototype._parsePLTE = function(data) {
|
Parser.prototype._parseIEND = function (data) {
|
||||||
|
this._crc.write(data);
|
||||||
|
|
||||||
this._crc.write(data);
|
this._hasIEND = true;
|
||||||
|
this._handleChunkEnd();
|
||||||
|
|
||||||
var entries = Math.floor(data.length / 3);
|
if (this.finished) {
|
||||||
// console.log('Palette:', entries);
|
this.finished();
|
||||||
|
}
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
15
lib/png-sync.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
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
Executable file → Normal file
|
|
@ -1,149 +1,193 @@
|
||||||
// Copyright (c) 2012 Kuba Niegowski
|
import Parser from "./parser-async.js";
|
||||||
//
|
import Packer from "./packer-async.js";
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
import PNGSync from "./png-sync.js";
|
||||||
// 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';
|
let util = require("util");
|
||||||
|
let Stream = require("stream");
|
||||||
|
|
||||||
|
let PNG = (exports.PNG = function (options) {
|
||||||
|
Stream.call(this);
|
||||||
|
|
||||||
var util = require('util'),
|
options = options || {}; // eslint-disable-line no-param-reassign
|
||||||
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;
|
||||||
|
|
||||||
var PNG = exports.PNG = function(options) {
|
this.data =
|
||||||
Stream.call(this);
|
this.width > 0 && this.height > 0
|
||||||
|
? Buffer.alloc(4 * this.width * this.height)
|
||||||
|
: null;
|
||||||
|
|
||||||
options = options || {};
|
if (options.fill && this.data) {
|
||||||
|
this.data.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
this.width = options.width || 0;
|
this.gamma = 0;
|
||||||
this.height = options.height || 0;
|
this.readable = this.writable = true;
|
||||||
|
|
||||||
this.data = this.width > 0 && this.height > 0
|
|
||||||
? new Buffer(4 * this.width * this.height) : null;
|
|
||||||
|
|
||||||
if(options.fill && this.data){this.data.fill(0)};
|
this._parser = new Parser(options);
|
||||||
|
|
||||||
this.gamma = 0;
|
this._parser.on("error", this.emit.bind(this, "error"));
|
||||||
this.readable = this.writable = true;
|
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._parser = new Parser(options || {});
|
this._packer = new Packer(options);
|
||||||
|
this._packer.on("data", this.emit.bind(this, "data"));
|
||||||
this._parser.on('error', this.emit.bind(this, 'error'));
|
this._packer.on("end", this.emit.bind(this, "end"));
|
||||||
this._parser.on('close', this._handleClose.bind(this));
|
this._parser.on("close", this._handleClose.bind(this));
|
||||||
this._parser.on('metadata', this._metadata.bind(this));
|
this._packer.on("error", this.emit.bind(this, "error"));
|
||||||
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);
|
util.inherits(PNG, Stream);
|
||||||
|
|
||||||
|
PNG.sync = PNGSync;
|
||||||
|
|
||||||
PNG.prototype.pack = function() {
|
PNG.prototype.pack = function () {
|
||||||
|
if (!this.data || !this.data.length) {
|
||||||
process.nextTick(function() {
|
this.emit("error", "No data provided");
|
||||||
this._packer.pack(this.data, this.width, this.height);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return 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;
|
||||||
|
|
||||||
PNG.prototype.parse = function(data, callback) {
|
onParsed = function (parsedData) {
|
||||||
|
this.removeListener("error", onError);
|
||||||
|
|
||||||
if (callback) {
|
this.data = parsedData;
|
||||||
var onParsed = null, onError = null;
|
callback(null, this);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
this.once('parsed', onParsed = function(data) {
|
onError = function (err) {
|
||||||
this.removeListener('error', onError);
|
this.removeListener("parsed", onParsed);
|
||||||
|
|
||||||
this.data = data;
|
callback(err, null);
|
||||||
callback(null, this);
|
}.bind(this);
|
||||||
|
|
||||||
}.bind(this));
|
this.once("parsed", onParsed);
|
||||||
|
this.once("error", onError);
|
||||||
|
}
|
||||||
|
|
||||||
this.once('error', onError = function(err) {
|
this.end(data);
|
||||||
this.removeListener('parsed', onParsed);
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
callback(err, null);
|
PNG.prototype.write = function (data) {
|
||||||
}.bind(this));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
src.gamma = 0;
|
||||||
this.end(data);
|
}
|
||||||
return this;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PNG.prototype.write = function(data) {
|
PNG.prototype.adjustGamma = function () {
|
||||||
this._parser.write(data);
|
PNG.adjustGamma(this);
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
163
lib/sync-inflate.js
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
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;
|
||||||
45
lib/sync-reader.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
};
|
||||||
68
package.json
|
|
@ -1,29 +1,73 @@
|
||||||
{
|
{
|
||||||
"name": "node-png",
|
"name": "pngjs",
|
||||||
"version": "0.4.3",
|
"version": "5.0.0",
|
||||||
"description": "Simple PNG encoder/decoder",
|
"description": "PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.",
|
||||||
"author": "Kuba Niegowski",
|
"contributors": [
|
||||||
"contributors": [],
|
"Alexandre Paré",
|
||||||
"homepage": "https://github.com/niegowski/node-pngjs/",
|
"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",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"png"
|
"PNG",
|
||||||
|
"decoder",
|
||||||
|
"encoder",
|
||||||
|
"js-png",
|
||||||
|
"node-png",
|
||||||
|
"parser",
|
||||||
|
"png",
|
||||||
|
"png-js",
|
||||||
|
"png-parse",
|
||||||
|
"pngjs"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "0.8.x"
|
"node": ">=10.13.0"
|
||||||
},
|
},
|
||||||
"main": "./lib/png.js",
|
"main": "./lib/png.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
"example": "examples"
|
"lib": "lib",
|
||||||
|
"example": "examples",
|
||||||
|
"test": "test"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"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 ."
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/brighthas/node-pngjs.git"
|
"url": "git://github.com/lukeapage/pngjs.git"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/brighthas/node-pngjs/issues"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
test/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
bg.png
|
||||||
BIN
test/bg-ref.png
Normal file
|
After Width: | Height: | Size: 119 B |
46
test/bg-spec.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/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();
|
||||||
|
});
|
||||||
|
});
|
||||||
BIN
test/bg.png
Normal file
|
After Width: | Height: | Size: 119 B |
105
test/convert-images-spec.js
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
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();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
16
test/http-server.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
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/");
|
||||||
409
test/imagediff.js
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
// 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;
|
||||||
|
});
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
PNGSUITE
|
PNGSUITE
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
testset for PNG-(de)coders
|
testset for PNG-(de)coders
|
||||||
created by Willem van Schaik
|
created by Willem van Schaik
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
This is a collection of graphics images created to test the png applications
|
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)
|
like viewers, converters and editors. All (as far as that is possible)
|
||||||
formats supported by the PNG standard are represented.
|
formats supported by the PNG standard are represented.
|
||||||
|
|
||||||
The suite consists of the following files:
|
The suite consists of the following files:
|
||||||
|
|
||||||
- PngSuite.README - this file
|
- PngSuite.README - this file
|
||||||
- PngSuite.LICENSE - the PngSuite is freeware
|
- PngSuite.LICENSE - the PngSuite is freeware
|
||||||
- PngSuite.png - image with PngSuite logo
|
- PngSuite.png - image with PngSuite logo
|
||||||
- PngSuite.tgz - archive of all PNG testfiles
|
- PngSuite.tgz - archive of all PNG testfiles
|
||||||
- PngSuite.zip - same in .zip format for PCs
|
- PngSuite.zip - same in .zip format for PCs
|
||||||
|
|
||||||
|
|
||||||
--------
|
--------
|
||||||
(c) Willem van Schaik
|
(c) Willem van Schaik
|
||||||
willem@schaik.com
|
willem@schaik.com
|
||||||
Calgary, April 2011
|
Calgary, April 2011
|
||||||
|
|
||||||
BIN
test/in/PngSuite.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
test/in/basi0g01.png
Normal file
|
After Width: | Height: | Size: 217 B |
BIN
test/in/basi0g02.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
test/in/basi0g04.png
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
test/in/basi0g08.png
Normal file
|
After Width: | Height: | Size: 254 B |
BIN
test/in/basi0g16.png
Normal file
|
After Width: | Height: | Size: 299 B |
BIN
test/in/basi2c08.png
Normal file
|
After Width: | Height: | Size: 315 B |
BIN
test/in/basi2c16.png
Normal file
|
After Width: | Height: | Size: 595 B |
BIN
test/in/basi3p01.png
Normal file
|
After Width: | Height: | Size: 132 B |
BIN
test/in/basi3p02.png
Normal file
|
After Width: | Height: | Size: 193 B |
BIN
test/in/basi3p04.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
test/in/basi3p08.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
test/in/basi4a08.png
Normal file
|
After Width: | Height: | Size: 214 B |
BIN
test/in/basi4a16.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
test/in/basi6a08.png
Normal file
|
After Width: | Height: | Size: 361 B |
BIN
test/in/basi6a16.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
test/in/basn0g01.png
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
test/in/basn0g02.png
Normal file
|
After Width: | Height: | Size: 104 B |
BIN
test/in/basn0g04.png
Normal file
|
After Width: | Height: | Size: 145 B |
|
Before Width: | Height: | Size: 138 B After Width: | Height: | Size: 138 B |
BIN
test/in/basn0g16.png
Normal file
|
After Width: | Height: | Size: 167 B |
|
Before Width: | Height: | Size: 145 B After Width: | Height: | Size: 145 B |
BIN
test/in/basn2c16.png
Normal file
|
After Width: | Height: | Size: 302 B |
BIN
test/in/basn3p01.png
Normal file
|
After Width: | Height: | Size: 112 B |
BIN
test/in/basn3p02.png
Normal file
|
After Width: | Height: | Size: 146 B |
BIN
test/in/basn3p04.png
Normal file
|
After Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 126 B After Width: | Height: | Size: 126 B |
BIN
test/in/basn4a16.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 184 B |
BIN
test/in/basn6a16.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
test/in/bgai4a08.png
Normal file
|
After Width: | Height: | Size: 214 B |
BIN
test/in/bgai4a16.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
test/in/bgan6a08.png
Normal file
|
After Width: | Height: | Size: 184 B |
BIN
test/in/bgan6a16.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
test/in/bgbn4a08.png
Normal file
|
After Width: | Height: | Size: 140 B |
BIN
test/in/bggn4a16.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
test/in/bgwn6a08.png
Normal file
|
After Width: | Height: | Size: 202 B |
BIN
test/in/bgyn6a16.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
test/in/ccwn2c08.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
test/in/ccwn3p08.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
test/in/cdfn2c08.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
test/in/cdhn2c08.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
test/in/cdsn2c08.png
Normal file
|
After Width: | Height: | Size: 232 B |
BIN
test/in/cdun2c08.png
Normal file
|
After Width: | Height: | Size: 724 B |
BIN
test/in/ch1n3p04.png
Normal file
|
After Width: | Height: | Size: 258 B |
BIN
test/in/ch2n3p08.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
test/in/cm0n0g04.png
Normal file
|
After Width: | Height: | Size: 292 B |