Compare commits
No commits in common. "master" and "0.4.3" have entirely different histories.
|
|
@ -1,2 +0,0 @@
|
||||||
/browser.js
|
|
||||||
/test/imagediff.js
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"parserOptions": { "ecmaVersion": 2018 },
|
|
||||||
"extends": ["eslint:recommended", "prettier"],
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
"es6": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-var": "error"
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": ["test/*.js", "examples/*.js"],
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
5
.gitignore
vendored
|
|
@ -1,6 +1 @@
|
||||||
node_modules
|
node_modules
|
||||||
.idea
|
|
||||||
coverage
|
|
||||||
examples/*.png
|
|
||||||
browser\.js
|
|
||||||
.nyc_output
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
test
|
|
||||||
examples
|
|
||||||
.travis.yml
|
|
||||||
appveyor.yml
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
/browser.js
|
|
||||||
/test/imagediff.js
|
|
||||||
.nyc_output
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
language: node_js
|
|
||||||
after_success:
|
|
||||||
- if [ "$TRAVIS_NODE_VERSION" = "10" ]; then yarn coverage && yarn codecov; fi
|
|
||||||
node_js:
|
|
||||||
- "10"
|
|
||||||
- "12"
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
3
LICENSE
|
|
@ -1,5 +1,4 @@
|
||||||
pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors
|
Copyright (c) 2012 Kuba Niegowski
|
||||||
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,411 +1,150 @@
|
||||||
[](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)
|
About
|
||||||
|
========
|
||||||
# pngjs
|
Simple PNG encoder/decoder for Node.js with no native dependencies.
|
||||||
|
|
||||||
*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 pngjs --save
|
$ npm install node-png
|
||||||
```
|
```
|
||||||
|
|
||||||
# Browser
|
Example
|
||||||
|
==========
|
||||||
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(
|
.pipe(new PNG({
|
||||||
new PNG({
|
filterType: 4
|
||||||
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;
|
|
||||||
|
|
||||||
// invert color
|
for (var y = 0; y < this.height; y++) {
|
||||||
this.data[idx] = 255 - this.data[idx];
|
for (var x = 0; x < this.width; x++) {
|
||||||
this.data[idx + 1] = 255 - this.data[idx + 1];
|
var idx = (this.width * y + x) << 2;
|
||||||
this.data[idx + 2] = 255 - this.data[idx + 2];
|
|
||||||
|
|
||||||
// and reduce opacity
|
// invert color
|
||||||
this.data[idx + 3] = this.data[idx + 3] >> 1;
|
this.data[idx] = 255 - this.data[idx];
|
||||||
}
|
this.data[idx+1] = 255 - this.data[idx+1];
|
||||||
}
|
this.data[idx+2] = 255 - this.data[idx+2];
|
||||||
|
|
||||||
this.pack().pipe(fs.createWriteStream("out.png"));
|
// and reduce opacity
|
||||||
});
|
this.data[idx+3] = this.data[idx+3] >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pack().pipe(fs.createWriteStream('out.png'));
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
For more examples see `examples` folder.
|
For more examples see `examples` folder.
|
||||||
|
|
||||||
# Async API
|
Documentation
|
||||||
|
================
|
||||||
|
|
||||||
As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported.
|
As input any color type is accepted (grayscale, rgb, palette, grayscale with alpha, rgb with alpha) but 8 bit per sample (channel) is the only supported bit depth. Interlaced mode is not supported.
|
||||||
|
|
||||||
## Class: PNG
|
### Supported ancillary chunks
|
||||||
|
- `gAMA` - gamma,
|
||||||
|
- `tRNS` - transparency (but only for paletted image)
|
||||||
|
|
||||||
|
|
||||||
|
## Class: PNG
|
||||||
`PNG` is readable and writable `Stream`.
|
`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 deflate (default: 9)
|
- `deflateLevel` - compression level for delate (default: 9)
|
||||||
- `deflateStrategy` - compression strategy for deflate (default: 3)
|
- `deflateStrategy` - compression strategy for delate (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) { }`
|
`function(data) { }`
|
||||||
Input image has been completely parsed, `data` is complete and ready for modification.
|
Input image has been completly parsed, `data` is complete and ready for modification.
|
||||||
|
|
||||||
|
|
||||||
### Event: "error"
|
### Event: "error"
|
||||||
|
|
||||||
`function(error) { }`
|
`function(error) { }`
|
||||||
|
|
||||||
### png.parse(data, [callback])
|
|
||||||
|
|
||||||
Parses PNG file data. Can be `String` or `Buffer`. Alternatively you can stream data to instance of PNG.
|
### png.parse(data, [callback])
|
||||||
|
Parses PNG file data. Alternatively you can stream data to instance of PNG.
|
||||||
|
|
||||||
Optional `callback` is once called on `error` or `parsed`. The callback gets
|
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)
|
|
||||||
|
|
||||||
Helper for image manipulation, copies a rectangle of pixels from current (i.e. the source) image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`).
|
### png.bitblt(dst, sx, sy, w, h, dx, dy)
|
||||||
|
Helper for image manipulation, copies rectangle of pixels from current image (`sx`, `sy`, `w`, `h`) to `dst` image (at `dx`, `dy`).
|
||||||
|
|
||||||
Returns `this` for method chaining.
|
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)
|
||||||
|
|
||||||
## Packing a PNG and removing alpha (RGBA to RGB)
|
Changelog
|
||||||
|
============
|
||||||
|
|
||||||
When removing the alpha channel from an image, there needs to be a background color to correctly
|
### 0.4.0 - Jun 05 2013
|
||||||
convert each pixel's transparency to the appropriate RGB value. By default, pngjs will flatten
|
- fixed reading of destroyed input stream
|
||||||
the image against a white background. You can override this in the options:
|
|
||||||
|
|
||||||
```js
|
### 0.4.0-alpha - 29 Nov 2012
|
||||||
var fs = require("fs"),
|
- added zlib deflateStrategy option, default to Z_RLE (by pdpotter)
|
||||||
PNG = require("pngjs").PNG;
|
- added possibility to use multiple filters (by pdpotter, modified by niegowski)
|
||||||
|
|
||||||
fs.createReadStream("in.png")
|
### 0.3.0-alpha - 23 Aug 2012
|
||||||
.pipe(
|
- Processing data as Streams, not complete Buffers of data
|
||||||
new PNG({
|
|
||||||
colorType: 2,
|
|
||||||
bgColor: {
|
|
||||||
red: 0,
|
|
||||||
green: 255,
|
|
||||||
blue: 0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.on("parsed", function () {
|
|
||||||
this.pack().pipe(fs.createWriteStream("out.png"));
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
# Sync API
|
### 0.2.0-alpha - 21 Aug 2012
|
||||||
|
- Input added palette, grayscale, no alpha support
|
||||||
|
- Better scanline filter selection
|
||||||
|
|
||||||
## PNG.sync
|
### 0.1.0-alpha - 19 Aug 2012
|
||||||
|
- First version
|
||||||
|
|
||||||
### PNG.sync.read(buffer)
|
License
|
||||||
|
=========
|
||||||
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
|
|
@ -1,28 +0,0 @@
|
||||||
version: "{build}"
|
|
||||||
|
|
||||||
clone_depth: 10
|
|
||||||
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- nodejs_version: "10"
|
|
||||||
- nodejs_version: "12"
|
|
||||||
|
|
||||||
install:
|
|
||||||
- ps: Install-Product node $env:nodejs_version
|
|
||||||
- npm install
|
|
||||||
|
|
||||||
build: off
|
|
||||||
|
|
||||||
test_script:
|
|
||||||
- node --version && npm --version
|
|
||||||
- npm test
|
|
||||||
|
|
||||||
cache:
|
|
||||||
- node_modules # local npm modules
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
let fs = require("fs");
|
|
||||||
let PNG = require("../lib/png").PNG;
|
|
||||||
let w = 32;
|
|
||||||
let h = 64;
|
|
||||||
|
|
||||||
/// RGBA input (color type 6)
|
|
||||||
let buffer = Buffer.alloc(2 * w * h * 4);
|
|
||||||
let bitmap = new Uint16Array(buffer.buffer);
|
|
||||||
for (let i = 0; i < h; i++) {
|
|
||||||
for (let j = 0; j < w; j++) {
|
|
||||||
bitmap[i * 4 * w + 4 * j] = (i * 65535) / h;
|
|
||||||
bitmap[i * 4 * w + 4 * j + 1] = (j * 65535) / w;
|
|
||||||
bitmap[i * 4 * w + 4 * j + 2] = ((h - i) * 65535) / h;
|
|
||||||
bitmap[i * 4 * w + 4 * j + 3] = 65535;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let png = new PNG({
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
bitDepth: 16,
|
|
||||||
colorType: 6,
|
|
||||||
inputColorType: 6,
|
|
||||||
inputHasAlpha: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
png.data = buffer;
|
|
||||||
png.pack().pipe(fs.createWriteStream("colortype6.png"));
|
|
||||||
|
|
||||||
//////// Grayscale 16 bits///////
|
|
||||||
|
|
||||||
buffer = Buffer.alloc(2 * w * h);
|
|
||||||
bitmap = new Uint16Array(buffer.buffer);
|
|
||||||
for (let i = 0; i < h; i++) {
|
|
||||||
for (let j = 0; j < w; j++) bitmap[i * w + j] = (i * 65535) / h;
|
|
||||||
}
|
|
||||||
|
|
||||||
png = new PNG({
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
bitDepth: 16,
|
|
||||||
colorType: 0,
|
|
||||||
inputColorType: 0,
|
|
||||||
inputHasAlpha: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
png.data = buffer;
|
|
||||||
png.pack().pipe(fs.createWriteStream("colortype0.png"));
|
|
||||||
|
|
@ -1,22 +1,26 @@
|
||||||
let fs = require("fs"),
|
|
||||||
PNG = require("../lib/png").PNG; // note require('pngjs') outside this project
|
|
||||||
|
|
||||||
fs.createReadStream("test/in/basi0g01.png")
|
var fs = require('fs'),
|
||||||
.pipe(new PNG({}))
|
PNG = require('pngjs').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;
|
|
||||||
|
|
||||||
// invert color
|
fs.createReadStream('in.png')
|
||||||
this.data[idx] = 255 - this.data[idx];
|
.pipe(new PNG({
|
||||||
this.data[idx + 1] = 255 - this.data[idx + 1];
|
filterType: 4
|
||||||
this.data[idx + 2] = 255 - this.data[idx + 2];
|
}))
|
||||||
|
.on('parsed', function() {
|
||||||
|
|
||||||
// and reduce opacity
|
for (var y = 0; y < this.height; y++) {
|
||||||
this.data[idx + 3] = this.data[idx + 3] >> 1;
|
for (var x = 0; x < this.width; x++) {
|
||||||
}
|
var idx = (this.width * y + x) << 2;
|
||||||
}
|
|
||||||
|
|
||||||
this.pack().pipe(fs.createWriteStream("out.png"));
|
// invert color
|
||||||
});
|
this.data[idx] = 255 - this.data[idx];
|
||||||
|
this.data[idx+1] = 255 - this.data[idx+1];
|
||||||
|
this.data[idx+2] = 255 - this.data[idx+2];
|
||||||
|
|
||||||
|
// and reduce opacity
|
||||||
|
this.data[idx+3] = this.data[idx+3] >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pack().pipe(fs.createWriteStream('out.png'));
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
let PNG = require("../lib/png").PNG;
|
|
||||||
let fs = require("fs");
|
|
||||||
|
|
||||||
let newfile = new PNG({ width: 10, height: 10 });
|
|
||||||
|
|
||||||
for (let y = 0; y < newfile.height; y++) {
|
|
||||||
for (let x = 0; x < newfile.width; x++) {
|
|
||||||
let idx = (newfile.width * y + x) << 2;
|
|
||||||
|
|
||||||
let col =
|
|
||||||
(x < newfile.width >> 1) ^ (y < newfile.height >> 1) ? 0xe5 : 0xff;
|
|
||||||
|
|
||||||
newfile.data[idx] = col;
|
|
||||||
newfile.data[idx + 1] = col;
|
|
||||||
newfile.data[idx + 2] = col;
|
|
||||||
newfile.data[idx + 3] = 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newfile
|
|
||||||
.pack()
|
|
||||||
.pipe(fs.createWriteStream(__dirname + "/newfile.png"))
|
|
||||||
.on("finish", function () {
|
|
||||||
console.log("Written!");
|
|
||||||
});
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
let fs = require("fs");
|
|
||||||
let PNG = require("../lib/png").PNG;
|
|
||||||
let w = 320;
|
|
||||||
let h = 200;
|
|
||||||
|
|
||||||
let bitmapWithoutAlpha = Buffer.alloc(w * h * 3);
|
|
||||||
let ofs = 0;
|
|
||||||
for (let i = 0; i < bitmapWithoutAlpha.length; i += 3) {
|
|
||||||
bitmapWithoutAlpha[ofs++] = 0xff;
|
|
||||||
bitmapWithoutAlpha[ofs++] = i % 0xff;
|
|
||||||
bitmapWithoutAlpha[ofs++] = (i / 3) % 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
let png = new PNG({
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
bitDepth: 8,
|
|
||||||
colorType: 2,
|
|
||||||
inputHasAlpha: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
png.data = bitmapWithoutAlpha;
|
|
||||||
png.pack().pipe(fs.createWriteStream("colortype2.png"));
|
|
||||||
|
|
||||||
png = new PNG({
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
bitDepth: 8,
|
|
||||||
colorType: 6,
|
|
||||||
inputHasAlpha: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
png.data = bitmapWithoutAlpha;
|
|
||||||
png.pack().pipe(fs.createWriteStream("colortype6.png"));
|
|
||||||
|
|
@ -1,28 +1,30 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
let fs = require("fs"),
|
var 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");
|
|
||||||
|
|
||||||
png.on("parsed", function () {
|
var png = new PNG({
|
||||||
for (let y = 0; y < png.height; y++) {
|
filterType: -1
|
||||||
for (let x = 0; x < png.width; x++) {
|
}),
|
||||||
let idx = (png.width * y + x) << 2;
|
src = fs.createReadStream(process.argv[2]),
|
||||||
|
dst = fs.createWriteStream(process.argv[3] || 'out.png');
|
||||||
|
|
||||||
if (
|
|
||||||
Math.abs(png.data[idx] - png.data[idx + 1]) <= 1 &&
|
png.on('parsed', function() {
|
||||||
Math.abs(png.data[idx + 1] - png.data[idx + 2]) <= 1
|
|
||||||
)
|
for (var y = 0; y < png.height; y++) {
|
||||||
png.data[idx] = png.data[idx + 1] = png.data[idx + 2];
|
for (var x = 0; x < png.width; x++) {
|
||||||
|
var idx = (png.width * y + x) << 2;
|
||||||
|
|
||||||
|
if (Math.abs(png.data[idx] - png.data[idx+1]) <= 1
|
||||||
|
&& Math.abs(png.data[idx+1] - png.data[idx+2]) <= 1)
|
||||||
|
png.data[idx] = png.data[idx+1] = png.data[idx+2];
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
png.pack().pipe(dst);
|
png.pack().pipe(dst);
|
||||||
});
|
});
|
||||||
|
|
||||||
src.pipe(png);
|
src.pipe(png);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
let fs = require("fs"),
|
|
||||||
PNG = require("../lib/png").PNG;
|
|
||||||
|
|
||||||
let srcFname = process.argv[2],
|
|
||||||
dstFname = process.argv[3] || "out.png";
|
|
||||||
|
|
||||||
// Read a PNG file
|
|
||||||
let data = fs.readFileSync(srcFname);
|
|
||||||
// Parse it
|
|
||||||
let png = PNG.sync.read(data, {
|
|
||||||
filterType: -1,
|
|
||||||
});
|
|
||||||
// Pack it back into a PNG data
|
|
||||||
let buff = PNG.sync.write(png);
|
|
||||||
// Write a PNG file
|
|
||||||
fs.writeFileSync(dstFname, buff);
|
|
||||||
27
examples/test/bg.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
PNG = require('pngjs').PNG;
|
||||||
|
|
||||||
|
|
||||||
|
var png = new PNG({
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
filterType: -1
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
for (var y = 0; y < png.height; y++) {
|
||||||
|
for (var x = 0; x < png.width; x++) {
|
||||||
|
var idx = (png.width * y + x) << 2;
|
||||||
|
|
||||||
|
var col = x < (png.width >> 1) ^ y < (png.height >> 1) ? 0xe5 : 0xff;
|
||||||
|
|
||||||
|
png.data[idx ] = col;
|
||||||
|
png.data[idx+1] = col;
|
||||||
|
png.data[idx+2] = col;
|
||||||
|
png.data[idx+3] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
png.pack().pipe(fs.createWriteStream(__dirname + '/bg.png'));
|
||||||
BIN
examples/test/bg.png
Normal file
|
After Width: | Height: | Size: 93 B |
|
Before Width: | Height: | Size: 138 B After Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 145 B After Width: | Height: | Size: 145 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 |
|
Before Width: | Height: | Size: 184 B After Width: | Height: | Size: 184 B |
|
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 319 B |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 389 B After Width: | Height: | Size: 389 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 985 B After Width: | Height: | Size: 985 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 719 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
38
examples/test/list.html
Executable file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>PNG Test</title>
|
||||||
|
<style>
|
||||||
|
body { background: #eee; font: 12px Arial; margin: 10px; }
|
||||||
|
img { margin: 2px; background: url(bg.png); }
|
||||||
|
h3 { margin: 10px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Basic</h3>
|
||||||
|
<img src="img/basn0g08.png"> <img src="out/basn0g08.png"> grayscale<br>
|
||||||
|
<img src="img/basn2c08.png"> <img src="out/basn2c08.png"> color<br>
|
||||||
|
<img src="img/basn3p08.png"> <img src="out/basn3p08.png"> paletted<br>
|
||||||
|
<img src="img/basn4a08.png"> <img src="out/basn4a08.png"> grayscale + alpha<br>
|
||||||
|
<img src="img/basn6a08.png"> <img src="out/basn6a08.png"> color + alpha<br>
|
||||||
|
|
||||||
|
<h3>Image filtering</h3>
|
||||||
|
<img src="img/f00n0g08.png"> <img src="out/f00n0g08.png"> grayscale, filter 0<br>
|
||||||
|
<img src="img/f00n2c08.png"> <img src="out/f00n2c08.png"> color, filter 0<br>
|
||||||
|
<img src="img/f01n0g08.png"> <img src="out/f01n0g08.png"> grayscale, filter 1<br>
|
||||||
|
<img src="img/f01n2c08.png"> <img src="out/f01n2c08.png"> color, filter 1<br>
|
||||||
|
<img src="img/f02n0g08.png"> <img src="out/f02n0g08.png"> grayscale, filter 2<br>
|
||||||
|
<img src="img/f02n2c08.png"> <img src="out/f02n2c08.png"> color, filter 2<br>
|
||||||
|
<img src="img/f03n0g08.png"> <img src="out/f03n0g08.png"> grayscale, filter 3<br>
|
||||||
|
<img src="img/f03n2c08.png"> <img src="out/f03n2c08.png"> color, filter 3<br>
|
||||||
|
<img src="img/f04n0g08.png"> <img src="out/f04n0g08.png"> grayscale, filter 4<br>
|
||||||
|
<img src="img/f04n2c08.png"> <img src="out/f04n2c08.png"> color, filter 4<br>
|
||||||
|
|
||||||
|
<h3>Transparency</h3>
|
||||||
|
<img src="img/tp0n0g08.png"> <img src="out/tp0n0g08.png"> grayscale, not transparent<br>
|
||||||
|
<img src="img/tp0n2c08.png"> <img src="out/tp0n2c08.png"> color, not transparent<br>
|
||||||
|
<img src="img/tp0n3p08.png"> <img src="out/tp0n3p08.png"> paletted, not transparent<br>
|
||||||
|
<img src="img/tbrn2c08.png"> <img src="out/tbrn2c08.png"> color, transparent<br>
|
||||||
|
<img src="img/tp1n3p08.png"> <img src="out/tp1n3p08.png"> paletted, transparent<br>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
examples/test/test.js
Executable file
|
|
@ -0,0 +1,38 @@
|
||||||
|
|
||||||
|
var fs = require('fs'),
|
||||||
|
PNG = require('pngjs').PNG;
|
||||||
|
|
||||||
|
|
||||||
|
fs.readdir(__dirname + '/img/', function(err, files) {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
files.forEach(function(file) {
|
||||||
|
|
||||||
|
if (!file.match(/\.png$/i))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fs.createReadStream(__dirname + '/img/' + file)
|
||||||
|
.pipe(new PNG())
|
||||||
|
.on('parsed', function() {
|
||||||
|
|
||||||
|
if (this.gamma) {
|
||||||
|
for (var y = 0; y < this.height; y++) {
|
||||||
|
for (var x = 0; x < this.width; x++) {
|
||||||
|
var idx = (this.width * y + x) << 2;
|
||||||
|
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
var sample = this.data[idx + i] / 255;
|
||||||
|
sample = Math.pow(sample, 1 / 2.2 / this.gamma);
|
||||||
|
this.data[idx + i] = Math.round(sample * 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pack()
|
||||||
|
.pipe(fs.createWriteStream(__dirname + '/out/' + file));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
265
lib/bitmapper.js
|
|
@ -1,265 +0,0 @@
|
||||||
import interlaceUtils from "./interlace.js";
|
|
||||||
|
|
||||||
let pixelBppMapper = [
|
|
||||||
// 0 - dummy entry
|
|
||||||
function () {},
|
|
||||||
|
|
||||||
// 1 - L
|
|
||||||
// 0: 0, 1: 0, 2: 0, 3: 0xff
|
|
||||||
function (pxData, data, pxPos, rawPos) {
|
|
||||||
if (rawPos === data.length) {
|
|
||||||
throw new Error("Ran out of data");
|
|
||||||
}
|
|
||||||
|
|
||||||
let pixel = data[rawPos];
|
|
||||||
pxData[pxPos] = pixel;
|
|
||||||
pxData[pxPos + 1] = pixel;
|
|
||||||
pxData[pxPos + 2] = pixel;
|
|
||||||
pxData[pxPos + 3] = 0xff;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 2 - LA
|
|
||||||
// 0: 0, 1: 0, 2: 0, 3: 1
|
|
||||||
function (pxData, data, pxPos, rawPos) {
|
|
||||||
if (rawPos + 1 >= data.length) {
|
|
||||||
throw new Error("Ran out of data");
|
|
||||||
}
|
|
||||||
|
|
||||||
let pixel = data[rawPos];
|
|
||||||
pxData[pxPos] = pixel;
|
|
||||||
pxData[pxPos + 1] = pixel;
|
|
||||||
pxData[pxPos + 2] = pixel;
|
|
||||||
pxData[pxPos + 3] = data[rawPos + 1];
|
|
||||||
},
|
|
||||||
|
|
||||||
// 3 - RGB
|
|
||||||
// 0: 0, 1: 1, 2: 2, 3: 0xff
|
|
||||||
function (pxData, data, pxPos, rawPos) {
|
|
||||||
if (rawPos + 2 >= data.length) {
|
|
||||||
throw new Error("Ran out of data");
|
|
||||||
}
|
|
||||||
|
|
||||||
pxData[pxPos] = data[rawPos];
|
|
||||||
pxData[pxPos + 1] = data[rawPos + 1];
|
|
||||||
pxData[pxPos + 2] = data[rawPos + 2];
|
|
||||||
pxData[pxPos + 3] = 0xff;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 4 - RGBA
|
|
||||||
// 0: 0, 1: 1, 2: 2, 3: 3
|
|
||||||
function (pxData, data, pxPos, rawPos) {
|
|
||||||
if (rawPos + 3 >= data.length) {
|
|
||||||
throw new Error("Ran out of data");
|
|
||||||
}
|
|
||||||
|
|
||||||
pxData[pxPos] = data[rawPos];
|
|
||||||
pxData[pxPos + 1] = data[rawPos + 1];
|
|
||||||
pxData[pxPos + 2] = data[rawPos + 2];
|
|
||||||
pxData[pxPos + 3] = data[rawPos + 3];
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let pixelBppCustomMapper = [
|
|
||||||
// 0 - dummy entry
|
|
||||||
function () {},
|
|
||||||
|
|
||||||
// 1 - L
|
|
||||||
// 0: 0, 1: 0, 2: 0, 3: 0xff
|
|
||||||
function (pxData, pixelData, pxPos, maxBit) {
|
|
||||||
let pixel = pixelData[0];
|
|
||||||
pxData[pxPos] = pixel;
|
|
||||||
pxData[pxPos + 1] = pixel;
|
|
||||||
pxData[pxPos + 2] = pixel;
|
|
||||||
pxData[pxPos + 3] = maxBit;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 2 - LA
|
|
||||||
// 0: 0, 1: 0, 2: 0, 3: 1
|
|
||||||
function (pxData, pixelData, pxPos) {
|
|
||||||
let pixel = pixelData[0];
|
|
||||||
pxData[pxPos] = pixel;
|
|
||||||
pxData[pxPos + 1] = pixel;
|
|
||||||
pxData[pxPos + 2] = pixel;
|
|
||||||
pxData[pxPos + 3] = pixelData[1];
|
|
||||||
},
|
|
||||||
|
|
||||||
// 3 - RGB
|
|
||||||
// 0: 0, 1: 1, 2: 2, 3: 0xff
|
|
||||||
function (pxData, pixelData, pxPos, maxBit) {
|
|
||||||
pxData[pxPos] = pixelData[0];
|
|
||||||
pxData[pxPos + 1] = pixelData[1];
|
|
||||||
pxData[pxPos + 2] = pixelData[2];
|
|
||||||
pxData[pxPos + 3] = maxBit;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 4 - RGBA
|
|
||||||
// 0: 0, 1: 1, 2: 2, 3: 3
|
|
||||||
function (pxData, pixelData, pxPos) {
|
|
||||||
pxData[pxPos] = pixelData[0];
|
|
||||||
pxData[pxPos + 1] = pixelData[1];
|
|
||||||
pxData[pxPos + 2] = pixelData[2];
|
|
||||||
pxData[pxPos + 3] = pixelData[3];
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function bitRetriever(data, depth) {
|
|
||||||
let leftOver = [];
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
function split() {
|
|
||||||
if (i === data.length) {
|
|
||||||
throw new Error("Ran out of data");
|
|
||||||
}
|
|
||||||
let byte = data[i];
|
|
||||||
i++;
|
|
||||||
let byte8, byte7, byte6, byte5, byte4, byte3, byte2, byte1;
|
|
||||||
switch (depth) {
|
|
||||||
default:
|
|
||||||
throw new Error("unrecognised depth");
|
|
||||||
case 16:
|
|
||||||
byte2 = data[i];
|
|
||||||
i++;
|
|
||||||
leftOver.push((byte << 8) + byte2);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
byte2 = byte & 0x0f;
|
|
||||||
byte1 = byte >> 4;
|
|
||||||
leftOver.push(byte1, byte2);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
byte4 = byte & 3;
|
|
||||||
byte3 = (byte >> 2) & 3;
|
|
||||||
byte2 = (byte >> 4) & 3;
|
|
||||||
byte1 = (byte >> 6) & 3;
|
|
||||||
leftOver.push(byte1, byte2, byte3, byte4);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
byte8 = byte & 1;
|
|
||||||
byte7 = (byte >> 1) & 1;
|
|
||||||
byte6 = (byte >> 2) & 1;
|
|
||||||
byte5 = (byte >> 3) & 1;
|
|
||||||
byte4 = (byte >> 4) & 1;
|
|
||||||
byte3 = (byte >> 5) & 1;
|
|
||||||
byte2 = (byte >> 6) & 1;
|
|
||||||
byte1 = (byte >> 7) & 1;
|
|
||||||
leftOver.push(byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
get: function (count) {
|
|
||||||
while (leftOver.length < count) {
|
|
||||||
split();
|
|
||||||
}
|
|
||||||
let returner = leftOver.slice(0, count);
|
|
||||||
leftOver = leftOver.slice(count);
|
|
||||||
return returner;
|
|
||||||
},
|
|
||||||
resetAfterLine: function () {
|
|
||||||
leftOver.length = 0;
|
|
||||||
},
|
|
||||||
end: function () {
|
|
||||||
if (i !== data.length) {
|
|
||||||
throw new Error("extra data found");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapImage8Bit(image, pxData, getPxPos, bpp, data, rawPos) {
|
|
||||||
// eslint-disable-line max-params
|
|
||||||
let imageWidth = image.width;
|
|
||||||
let imageHeight = image.height;
|
|
||||||
let imagePass = image.index;
|
|
||||||
for (let y = 0; y < imageHeight; y++) {
|
|
||||||
for (let x = 0; x < imageWidth; x++) {
|
|
||||||
let pxPos = getPxPos(x, y, imagePass);
|
|
||||||
pixelBppMapper[bpp](pxData, data, pxPos, rawPos);
|
|
||||||
rawPos += bpp; //eslint-disable-line no-param-reassign
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rawPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapImageCustomBit(image, pxData, getPxPos, bpp, bits, maxBit) {
|
|
||||||
// eslint-disable-line max-params
|
|
||||||
let imageWidth = image.width;
|
|
||||||
let imageHeight = image.height;
|
|
||||||
let imagePass = image.index;
|
|
||||||
for (let y = 0; y < imageHeight; y++) {
|
|
||||||
for (let x = 0; x < imageWidth; x++) {
|
|
||||||
let pixelData = bits.get(bpp);
|
|
||||||
let pxPos = getPxPos(x, y, imagePass);
|
|
||||||
pixelBppCustomMapper[bpp](pxData, pixelData, pxPos, maxBit);
|
|
||||||
}
|
|
||||||
bits.resetAfterLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (data, bitmapInfo) {
|
|
||||||
let width = bitmapInfo.width;
|
|
||||||
let height = bitmapInfo.height;
|
|
||||||
let depth = bitmapInfo.depth;
|
|
||||||
let bpp = bitmapInfo.bpp;
|
|
||||||
let interlace = bitmapInfo.interlace;
|
|
||||||
let bits;
|
|
||||||
|
|
||||||
if (depth !== 8) {
|
|
||||||
bits = bitRetriever(data, depth);
|
|
||||||
}
|
|
||||||
let pxData;
|
|
||||||
if (depth <= 8) {
|
|
||||||
pxData = Buffer.alloc(width * height * 4);
|
|
||||||
} else {
|
|
||||||
pxData = new Uint16Array(width * height * 4);
|
|
||||||
}
|
|
||||||
let maxBit = Math.pow(2, depth) - 1;
|
|
||||||
let rawPos = 0;
|
|
||||||
let images;
|
|
||||||
let getPxPos;
|
|
||||||
|
|
||||||
if (interlace) {
|
|
||||||
images = interlaceUtils.getImagePasses(width, height);
|
|
||||||
getPxPos = interlaceUtils.getInterlaceIterator(width, height);
|
|
||||||
} else {
|
|
||||||
let nonInterlacedPxPos = 0;
|
|
||||||
getPxPos = function () {
|
|
||||||
let returner = nonInterlacedPxPos;
|
|
||||||
nonInterlacedPxPos += 4;
|
|
||||||
return returner;
|
|
||||||
};
|
|
||||||
images = [{ width: width, height: height }];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
|
|
||||||
if (depth === 8) {
|
|
||||||
rawPos = mapImage8Bit(
|
|
||||||
images[imageIndex],
|
|
||||||
pxData,
|
|
||||||
getPxPos,
|
|
||||||
bpp,
|
|
||||||
data,
|
|
||||||
rawPos
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
mapImageCustomBit(
|
|
||||||
images[imageIndex],
|
|
||||||
pxData,
|
|
||||||
getPxPos,
|
|
||||||
bpp,
|
|
||||||
bits,
|
|
||||||
maxBit
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (depth === 8) {
|
|
||||||
if (rawPos !== data.length) {
|
|
||||||
throw new Error("extra data found");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bits.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
return pxData;
|
|
||||||
};
|
|
||||||
156
lib/bitpacker.js
|
|
@ -1,156 +0,0 @@
|
||||||
import constants from "./constants.js";
|
|
||||||
|
|
||||||
export default function (dataIn, width, height, options) {
|
|
||||||
let outHasAlpha =
|
|
||||||
[constants.COLORTYPE_COLOR_ALPHA, constants.COLORTYPE_ALPHA].indexOf(
|
|
||||||
options.colorType
|
|
||||||
) !== -1;
|
|
||||||
if (options.colorType === options.inputColorType) {
|
|
||||||
let bigEndian = (function () {
|
|
||||||
let buffer = new ArrayBuffer(2);
|
|
||||||
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
|
|
||||||
// Int16Array uses the platform's endianness.
|
|
||||||
return new Int16Array(buffer)[0] !== 256;
|
|
||||||
})();
|
|
||||||
// If no need to convert to grayscale and alpha is present/absent in both, take a fast route
|
|
||||||
if (options.bitDepth === 8 || (options.bitDepth === 16 && bigEndian)) {
|
|
||||||
return dataIn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// map to a UInt16 array if data is 16bit, fix endianness below
|
|
||||||
let data = options.bitDepth !== 16 ? dataIn : new Uint16Array(dataIn.buffer);
|
|
||||||
|
|
||||||
let maxValue = 255;
|
|
||||||
let inBpp = constants.COLORTYPE_TO_BPP_MAP[options.inputColorType];
|
|
||||||
if (inBpp === 4 && !options.inputHasAlpha) {
|
|
||||||
inBpp = 3;
|
|
||||||
}
|
|
||||||
let outBpp = constants.COLORTYPE_TO_BPP_MAP[options.colorType];
|
|
||||||
if (options.bitDepth === 16) {
|
|
||||||
maxValue = 65535;
|
|
||||||
outBpp *= 2;
|
|
||||||
}
|
|
||||||
let outData = Buffer.alloc(width * height * outBpp);
|
|
||||||
|
|
||||||
let inIndex = 0;
|
|
||||||
let outIndex = 0;
|
|
||||||
|
|
||||||
let bgColor = options.bgColor || {};
|
|
||||||
if (bgColor.red === undefined) {
|
|
||||||
bgColor.red = maxValue;
|
|
||||||
}
|
|
||||||
if (bgColor.green === undefined) {
|
|
||||||
bgColor.green = maxValue;
|
|
||||||
}
|
|
||||||
if (bgColor.blue === undefined) {
|
|
||||||
bgColor.blue = maxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRGBA() {
|
|
||||||
let red;
|
|
||||||
let green;
|
|
||||||
let blue;
|
|
||||||
let alpha = maxValue;
|
|
||||||
switch (options.inputColorType) {
|
|
||||||
case constants.COLORTYPE_COLOR_ALPHA:
|
|
||||||
alpha = data[inIndex + 3];
|
|
||||||
red = data[inIndex];
|
|
||||||
green = data[inIndex + 1];
|
|
||||||
blue = data[inIndex + 2];
|
|
||||||
break;
|
|
||||||
case constants.COLORTYPE_COLOR:
|
|
||||||
red = data[inIndex];
|
|
||||||
green = data[inIndex + 1];
|
|
||||||
blue = data[inIndex + 2];
|
|
||||||
break;
|
|
||||||
case constants.COLORTYPE_ALPHA:
|
|
||||||
alpha = data[inIndex + 1];
|
|
||||||
red = data[inIndex];
|
|
||||||
green = red;
|
|
||||||
blue = red;
|
|
||||||
break;
|
|
||||||
case constants.COLORTYPE_GRAYSCALE:
|
|
||||||
red = data[inIndex];
|
|
||||||
green = red;
|
|
||||||
blue = red;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
"input color type:" +
|
|
||||||
options.inputColorType +
|
|
||||||
" is not supported at present"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.inputHasAlpha) {
|
|
||||||
if (!outHasAlpha) {
|
|
||||||
alpha /= maxValue;
|
|
||||||
red = Math.min(
|
|
||||||
Math.max(Math.round((1 - alpha) * bgColor.red + alpha * red), 0),
|
|
||||||
maxValue
|
|
||||||
);
|
|
||||||
green = Math.min(
|
|
||||||
Math.max(Math.round((1 - alpha) * bgColor.green + alpha * green), 0),
|
|
||||||
maxValue
|
|
||||||
);
|
|
||||||
blue = Math.min(
|
|
||||||
Math.max(Math.round((1 - alpha) * bgColor.blue + alpha * blue), 0),
|
|
||||||
maxValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { red: red, green: green, blue: blue, alpha: alpha };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
let rgba = getRGBA(data, inIndex);
|
|
||||||
|
|
||||||
switch (options.colorType) {
|
|
||||||
case constants.COLORTYPE_COLOR_ALPHA:
|
|
||||||
case constants.COLORTYPE_COLOR:
|
|
||||||
if (options.bitDepth === 8) {
|
|
||||||
outData[outIndex] = rgba.red;
|
|
||||||
outData[outIndex + 1] = rgba.green;
|
|
||||||
outData[outIndex + 2] = rgba.blue;
|
|
||||||
if (outHasAlpha) {
|
|
||||||
outData[outIndex + 3] = rgba.alpha;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
outData.writeUInt16BE(rgba.red, outIndex);
|
|
||||||
outData.writeUInt16BE(rgba.green, outIndex + 2);
|
|
||||||
outData.writeUInt16BE(rgba.blue, outIndex + 4);
|
|
||||||
if (outHasAlpha) {
|
|
||||||
outData.writeUInt16BE(rgba.alpha, outIndex + 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case constants.COLORTYPE_ALPHA:
|
|
||||||
case constants.COLORTYPE_GRAYSCALE: {
|
|
||||||
// Convert to grayscale and alpha
|
|
||||||
let grayscale = (rgba.red + rgba.green + rgba.blue) / 3;
|
|
||||||
if (options.bitDepth === 8) {
|
|
||||||
outData[outIndex] = grayscale;
|
|
||||||
if (outHasAlpha) {
|
|
||||||
outData[outIndex + 1] = rgba.alpha;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
outData.writeUInt16BE(grayscale, outIndex);
|
|
||||||
if (outHasAlpha) {
|
|
||||||
outData.writeUInt16BE(rgba.alpha, outIndex + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error("unrecognised color Type " + options.colorType);
|
|
||||||
}
|
|
||||||
|
|
||||||
inIndex += inBpp;
|
|
||||||
outIndex += outBpp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return outData;
|
|
||||||
};
|
|
||||||
316
lib/chunkstream.js
Normal file → Executable file
|
|
@ -1,190 +1,200 @@
|
||||||
let util = require("util");
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
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.
|
||||||
|
|
||||||
let ChunkStream = function () {
|
'use strict';
|
||||||
Stream.call(this);
|
|
||||||
|
|
||||||
this._buffers = [];
|
|
||||||
this._buffered = 0;
|
|
||||||
|
|
||||||
this._reads = [];
|
var util = require('util'),
|
||||||
this._paused = false;
|
Stream = require('stream');
|
||||||
|
|
||||||
this._encoding = "utf8";
|
|
||||||
this.writable = true;
|
var ChunkStream = module.exports = function() {
|
||||||
|
Stream.call(this);
|
||||||
|
|
||||||
|
this._buffers = [];
|
||||||
|
this._buffered = 0;
|
||||||
|
|
||||||
|
this._reads = [];
|
||||||
|
this._paused = false;
|
||||||
|
|
||||||
|
this._encoding = 'utf8';
|
||||||
|
this.writable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChunkStream;
|
|
||||||
|
|
||||||
util.inherits(ChunkStream, Stream);
|
util.inherits(ChunkStream, Stream);
|
||||||
|
|
||||||
ChunkStream.prototype.read = function (length, callback) {
|
|
||||||
this._reads.push({
|
|
||||||
length: Math.abs(length), // if length < 0 then at most this length
|
|
||||||
allowLess: length < 0,
|
|
||||||
func: callback,
|
|
||||||
});
|
|
||||||
|
|
||||||
process.nextTick(
|
ChunkStream.prototype.read = function(length, callback) {
|
||||||
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 && this._reads.length > 0) {
|
if (this._paused && 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dataBuffer;
|
if (!this.writable) {
|
||||||
if (Buffer.isBuffer(data)) {
|
this.emit('error', new Error('Stream not writable'));
|
||||||
dataBuffer = data;
|
return false;
|
||||||
} else {
|
}
|
||||||
dataBuffer = Buffer.from(data, encoding || this._encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._buffers.push(dataBuffer);
|
if (!Buffer.isBuffer(data))
|
||||||
this._buffered += dataBuffer.length;
|
data = new Buffer(data, encoding || this._encoding);
|
||||||
|
|
||||||
this._process();
|
this._buffers.push(data);
|
||||||
|
this._buffered += data.length;
|
||||||
|
|
||||||
// ok if there are no more read requests
|
|
||||||
if (this._reads && this._reads.length === 0) {
|
|
||||||
this._paused = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.writable && !this._paused;
|
|
||||||
};
|
|
||||||
|
|
||||||
ChunkStream.prototype.end = function (data, encoding) {
|
|
||||||
if (data) {
|
|
||||||
this.write(data, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.writable = false;
|
|
||||||
|
|
||||||
// already destroyed
|
|
||||||
if (!this._buffers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// enqueue or handle end
|
|
||||||
if (this._buffers.length === 0) {
|
|
||||||
this._end();
|
|
||||||
} else {
|
|
||||||
this._buffers.push(null);
|
|
||||||
this._process();
|
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.destroy();
|
if (this._reads.length > 0) {
|
||||||
};
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// remove all used buffers
|
this.destroy();
|
||||||
if (count > 0) {
|
|
||||||
this._buffers.splice(0, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._buffered -= read.length;
|
|
||||||
|
|
||||||
read.func.call(this, data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ChunkStream.prototype._process = function () {
|
ChunkStream.prototype.destroy = 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];
|
|
||||||
|
|
||||||
// read any data (but no more than length)
|
var read = this._reads[0];
|
||||||
if (read.allowLess) {
|
|
||||||
this._processReadAllowingLess(read);
|
|
||||||
} else if (this._buffered >= read.length) {
|
|
||||||
// ok we can meet some expectations
|
|
||||||
|
|
||||||
this._processRead(read);
|
// read any data (but no more than length)
|
||||||
} else {
|
if (read.allowLess) {
|
||||||
// not enought data to satisfy first request in queue
|
|
||||||
// so we need to wait for more
|
// ok there is any data so that we can satisfy this request
|
||||||
break;
|
this._reads.shift(); // == read
|
||||||
}
|
|
||||||
|
// first we need to peek into first buffer
|
||||||
|
var buf = this._buffers[0];
|
||||||
|
|
||||||
|
// ok there is more data than we need
|
||||||
|
if (buf.length > read.length) {
|
||||||
|
|
||||||
|
this._buffered -= read.length;
|
||||||
|
this._buffers[0] = buf.slice(read.length);
|
||||||
|
|
||||||
|
read.func.call(this, buf.slice(0, read.length));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ok this is less than maximum length so use it all
|
||||||
|
this._buffered -= buf.length;
|
||||||
|
this._buffers.shift(); // == buf
|
||||||
|
|
||||||
|
read.func.call(this, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this._buffered >= read.length) {
|
||||||
|
// ok we can meet some expectations
|
||||||
|
|
||||||
|
this._reads.shift(); // == read
|
||||||
|
|
||||||
|
var pos = 0,
|
||||||
|
count = 0,
|
||||||
|
data = new Buffer(read.length);
|
||||||
|
|
||||||
|
// create buffer for all data
|
||||||
|
while (pos < read.length) {
|
||||||
|
|
||||||
|
var buf = this._buffers[count++],
|
||||||
|
len = Math.min(buf.length, read.length - pos);
|
||||||
|
|
||||||
|
buf.copy(data, pos, 0, len);
|
||||||
|
pos += len;
|
||||||
|
|
||||||
|
// last buffer wasn't used all so just slice it and leave
|
||||||
|
if (len != buf.length)
|
||||||
|
this._buffers[--count] = buf.slice(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove all used buffers
|
||||||
|
if (count > 0)
|
||||||
|
this._buffers.splice(0, count);
|
||||||
|
|
||||||
|
this._buffered -= read.length;
|
||||||
|
|
||||||
|
read.func.call(this, data);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// not enought data to satisfy first request in queue
|
||||||
|
// so we need to wait for more
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._buffers && !this.writable) {
|
if (this._buffers && this._buffers.length > 0 && this._buffers[0] == null) {
|
||||||
this._end();
|
this._end();
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
|
||||||
this.emit("error", ex);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
56
lib/constants.js
Normal file → Executable file
|
|
@ -1,30 +1,38 @@
|
||||||
export default {
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
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.
|
||||||
|
|
||||||
TYPE_IHDR: 0x49484452,
|
'use strict';
|
||||||
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
|
|
||||||
|
|
||||||
// color-type combinations
|
module.exports = {
|
||||||
COLORTYPE_PALETTE_COLOR: 3,
|
|
||||||
COLORTYPE_COLOR_ALPHA: 6,
|
|
||||||
|
|
||||||
COLORTYPE_TO_BPP_MAP: {
|
PNG_SIGNATURE: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
|
||||||
0: 1,
|
|
||||||
2: 3,
|
|
||||||
3: 1,
|
|
||||||
4: 2,
|
|
||||||
6: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
GAMMA_DIVISION: 100000,
|
TYPE_IHDR: 0x49484452,
|
||||||
|
TYPE_IEND: 0x49454e44,
|
||||||
|
TYPE_IDAT: 0x49444154,
|
||||||
|
TYPE_PLTE: 0x504c5445,
|
||||||
|
TYPE_tRNS: 0x74524e53,
|
||||||
|
TYPE_gAMA: 0x67414d41,
|
||||||
|
|
||||||
|
COLOR_PALETTE: 1,
|
||||||
|
COLOR_COLOR: 2,
|
||||||
|
COLOR_ALPHA: 4
|
||||||
};
|
};
|
||||||
|
|
|
||||||
99
lib/crc.js
Normal file → Executable file
|
|
@ -1,40 +1,79 @@
|
||||||
let crcTable = [];
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
(function () {
|
'use strict';
|
||||||
for (let i = 0; i < 256; i++) {
|
|
||||||
let currentCrc = i;
|
var util = require('util'),
|
||||||
for (let j = 0; j < 8; j++) {
|
Stream = require('stream');
|
||||||
if (currentCrc & 1) {
|
|
||||||
currentCrc = 0xedb88320 ^ (currentCrc >>> 1);
|
|
||||||
} else {
|
var CrcStream = module.exports = function() {
|
||||||
currentCrc = currentCrc >>> 1;
|
Stream.call(this);
|
||||||
}
|
|
||||||
|
this._crc = -1;
|
||||||
|
|
||||||
|
this.writable = true;
|
||||||
|
};
|
||||||
|
util.inherits(CrcStream, Stream);
|
||||||
|
|
||||||
|
|
||||||
|
CrcStream.prototype.write = function(data) {
|
||||||
|
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8);
|
||||||
}
|
}
|
||||||
crcTable[i] = currentCrc;
|
return true;
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
let CrcCalculator = function () {
|
|
||||||
this._crc = -1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CrcCalculator;
|
CrcStream.prototype.end = function(data) {
|
||||||
|
if (data) this.write(data);
|
||||||
|
|
||||||
CrcCalculator.prototype.write = function (data) {
|
this.emit('crc', this.crc32());
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
this._crc = crcTable[(this._crc ^ data[i]) & 0xff] ^ (this._crc >>> 8);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CrcCalculator.prototype.crc32 = function () {
|
CrcStream.prototype.crc32 = function() {
|
||||||
return this._crc ^ -1;
|
return this._crc ^ -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
CrcCalculator.crc32 = function (buf) {
|
|
||||||
let crc = -1;
|
CrcStream.crc32 = function(buf) {
|
||||||
for (let i = 0; i < buf.length; i++) {
|
|
||||||
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
var crc = -1;
|
||||||
}
|
for (var i = 0; i < buf.length; i++) {
|
||||||
return crc ^ -1;
|
crc = crcTable[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
||||||
|
}
|
||||||
|
return crc ^ -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var crcTable = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < 256; i++) {
|
||||||
|
var c = i;
|
||||||
|
for (var j = 0; j < 8; j++) {
|
||||||
|
if (c & 1) {
|
||||||
|
c = 0xedb88320 ^ (c >>> 1);
|
||||||
|
} else {
|
||||||
|
c = c >>> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crcTable[i] = c;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
import paethPredictor from "./paeth-predictor.js";
|
|
||||||
|
|
||||||
function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) {
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
rawData[rawPos + x] = pxData[pxPos + x];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterSumNone(pxData, pxPos, byteWidth) {
|
|
||||||
let sum = 0;
|
|
||||||
let length = pxPos + byteWidth;
|
|
||||||
|
|
||||||
for (let i = pxPos; i < length; i++) {
|
|
||||||
sum += Math.abs(pxData[i]);
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
|
|
||||||
let val = pxData[pxPos + x] - left;
|
|
||||||
|
|
||||||
rawData[rawPos + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterSumSub(pxData, pxPos, byteWidth, bpp) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
|
|
||||||
let val = pxData[pxPos + x] - left;
|
|
||||||
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) {
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
|
||||||
let val = pxData[pxPos + x] - up;
|
|
||||||
|
|
||||||
rawData[rawPos + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterSumUp(pxData, pxPos, byteWidth) {
|
|
||||||
let sum = 0;
|
|
||||||
let length = pxPos + byteWidth;
|
|
||||||
for (let x = pxPos; x < length; x++) {
|
|
||||||
let up = pxPos > 0 ? pxData[x - byteWidth] : 0;
|
|
||||||
let val = pxData[x] - up;
|
|
||||||
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
|
|
||||||
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
|
||||||
let val = pxData[pxPos + x] - ((left + up) >> 1);
|
|
||||||
|
|
||||||
rawData[rawPos + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterSumAvg(pxData, pxPos, byteWidth, bpp) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
|
|
||||||
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
|
||||||
let val = pxData[pxPos + x] - ((left + up) >> 1);
|
|
||||||
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
|
|
||||||
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
|
||||||
let upleft =
|
|
||||||
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
|
|
||||||
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
|
|
||||||
|
|
||||||
rawData[rawPos + x] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterSumPaeth(pxData, pxPos, byteWidth, bpp) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
|
|
||||||
let up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
|
|
||||||
let upleft =
|
|
||||||
pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
|
|
||||||
let val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
|
|
||||||
|
|
||||||
sum += Math.abs(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
let filters = {
|
|
||||||
0: filterNone,
|
|
||||||
1: filterSub,
|
|
||||||
2: filterUp,
|
|
||||||
3: filterAvg,
|
|
||||||
4: filterPaeth,
|
|
||||||
};
|
|
||||||
|
|
||||||
let filterSums = {
|
|
||||||
0: filterSumNone,
|
|
||||||
1: filterSumSub,
|
|
||||||
2: filterSumUp,
|
|
||||||
3: filterSumAvg,
|
|
||||||
4: filterSumPaeth,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function (pxData, width, height, options, bpp) {
|
|
||||||
let filterTypes;
|
|
||||||
if (!("filterType" in options) || options.filterType === -1) {
|
|
||||||
filterTypes = [0, 1, 2, 3, 4];
|
|
||||||
} else if (typeof options.filterType === "number") {
|
|
||||||
filterTypes = [options.filterType];
|
|
||||||
} else {
|
|
||||||
throw new Error("unrecognised filter types");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.bitDepth === 16) {
|
|
||||||
bpp *= 2;
|
|
||||||
}
|
|
||||||
let byteWidth = width * bpp;
|
|
||||||
let rawPos = 0;
|
|
||||||
let pxPos = 0;
|
|
||||||
let rawData = Buffer.alloc((byteWidth + 1) * height);
|
|
||||||
|
|
||||||
let sel = filterTypes[0];
|
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
if (filterTypes.length > 1) {
|
|
||||||
// find best filter for this line (with lowest sum of values)
|
|
||||||
let min = Infinity;
|
|
||||||
|
|
||||||
for (let i = 0; i < filterTypes.length; i++) {
|
|
||||||
let sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp);
|
|
||||||
if (sum < min) {
|
|
||||||
sel = filterTypes[i];
|
|
||||||
min = sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rawData[rawPos] = sel;
|
|
||||||
rawPos++;
|
|
||||||
filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp);
|
|
||||||
rawPos += byteWidth;
|
|
||||||
pxPos += byteWidth;
|
|
||||||
}
|
|
||||||
return rawData;
|
|
||||||
};
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import ChunkStream from "./chunkstream.js";
|
|
||||||
import Filter from "./filter-parse.js";
|
|
||||||
let util = require("util");
|
|
||||||
|
|
||||||
let FilterAsync = function (bitmapInfo) {
|
|
||||||
ChunkStream.call(this);
|
|
||||||
|
|
||||||
let buffers = [];
|
|
||||||
let that = this;
|
|
||||||
this._filter = new Filter(bitmapInfo, {
|
|
||||||
read: this.read.bind(this),
|
|
||||||
write: function (buffer) {
|
|
||||||
buffers.push(buffer);
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
that.emit("complete", Buffer.concat(buffers));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this._filter.start();
|
|
||||||
};
|
|
||||||
util.inherits(FilterAsync, ChunkStream);
|
|
||||||
|
|
||||||
export default FilterAsync;
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import SyncReader from "./sync-reader.js";
|
|
||||||
import Filter from "./filter-parse.js";
|
|
||||||
|
|
||||||
export default function (inBuffer, bitmapInfo) {
|
|
||||||
let outBuffers = [];
|
|
||||||
let reader = new SyncReader(inBuffer);
|
|
||||||
let filter = new Filter(bitmapInfo, {
|
|
||||||
read: reader.read.bind(reader),
|
|
||||||
write: function (bufferPart) {
|
|
||||||
outBuffers.push(bufferPart);
|
|
||||||
},
|
|
||||||
complete: function () {},
|
|
||||||
});
|
|
||||||
|
|
||||||
filter.start();
|
|
||||||
reader.process();
|
|
||||||
|
|
||||||
return Buffer.concat(outBuffers);
|
|
||||||
};
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
import interlaceUtils from "./interlace.js";
|
|
||||||
import paethPredictor from "./paeth-predictor.js";
|
|
||||||
|
|
||||||
function getByteWidth(width, bpp, depth) {
|
|
||||||
let byteWidth = width * bpp;
|
|
||||||
if (depth !== 8) {
|
|
||||||
byteWidth = Math.ceil(byteWidth / (8 / depth));
|
|
||||||
}
|
|
||||||
return byteWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Filter = function (bitmapInfo, dependencies) {
|
|
||||||
let width = bitmapInfo.width;
|
|
||||||
let height = bitmapInfo.height;
|
|
||||||
let interlace = bitmapInfo.interlace;
|
|
||||||
let bpp = bitmapInfo.bpp;
|
|
||||||
let depth = bitmapInfo.depth;
|
|
||||||
|
|
||||||
this.read = dependencies.read;
|
|
||||||
this.write = dependencies.write;
|
|
||||||
this.complete = dependencies.complete;
|
|
||||||
|
|
||||||
this._imageIndex = 0;
|
|
||||||
this._images = [];
|
|
||||||
if (interlace) {
|
|
||||||
let passes = interlaceUtils.getImagePasses(width, height);
|
|
||||||
for (let i = 0; i < passes.length; i++) {
|
|
||||||
this._images.push({
|
|
||||||
byteWidth: getByteWidth(passes[i].width, bpp, depth),
|
|
||||||
height: passes[i].height,
|
|
||||||
lineIndex: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._images.push({
|
|
||||||
byteWidth: getByteWidth(width, bpp, depth),
|
|
||||||
height: height,
|
|
||||||
lineIndex: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// when filtering the line we look at the pixel to the left
|
|
||||||
// the spec also says it is done on a byte level regardless of the number of pixels
|
|
||||||
// so if the depth is byte compatible (8 or 16) we subtract the bpp in order to compare back
|
|
||||||
// a pixel rather than just a different byte part. However if we are sub byte, we ignore.
|
|
||||||
if (depth === 8) {
|
|
||||||
this._xComparison = bpp;
|
|
||||||
} else if (depth === 16) {
|
|
||||||
this._xComparison = bpp * 2;
|
|
||||||
} else {
|
|
||||||
this._xComparison = 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Filter;
|
|
||||||
|
|
||||||
Filter.prototype.start = function () {
|
|
||||||
this.read(
|
|
||||||
this._images[this._imageIndex].byteWidth + 1,
|
|
||||||
this._reverseFilterLine.bind(this)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._unFilterType1 = function (
|
|
||||||
rawData,
|
|
||||||
unfilteredLine,
|
|
||||||
byteWidth
|
|
||||||
) {
|
|
||||||
let xComparison = this._xComparison;
|
|
||||||
let xBiggerThan = xComparison - 1;
|
|
||||||
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let rawByte = rawData[1 + x];
|
|
||||||
let f1Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
|
|
||||||
unfilteredLine[x] = rawByte + f1Left;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._unFilterType2 = function (
|
|
||||||
rawData,
|
|
||||||
unfilteredLine,
|
|
||||||
byteWidth
|
|
||||||
) {
|
|
||||||
let lastLine = this._lastLine;
|
|
||||||
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let rawByte = rawData[1 + x];
|
|
||||||
let f2Up = lastLine ? lastLine[x] : 0;
|
|
||||||
unfilteredLine[x] = rawByte + f2Up;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._unFilterType3 = function (
|
|
||||||
rawData,
|
|
||||||
unfilteredLine,
|
|
||||||
byteWidth
|
|
||||||
) {
|
|
||||||
let xComparison = this._xComparison;
|
|
||||||
let xBiggerThan = xComparison - 1;
|
|
||||||
let lastLine = this._lastLine;
|
|
||||||
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let rawByte = rawData[1 + x];
|
|
||||||
let f3Up = lastLine ? lastLine[x] : 0;
|
|
||||||
let f3Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
|
|
||||||
let f3Add = Math.floor((f3Left + f3Up) / 2);
|
|
||||||
unfilteredLine[x] = rawByte + f3Add;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._unFilterType4 = function (
|
|
||||||
rawData,
|
|
||||||
unfilteredLine,
|
|
||||||
byteWidth
|
|
||||||
) {
|
|
||||||
let xComparison = this._xComparison;
|
|
||||||
let xBiggerThan = xComparison - 1;
|
|
||||||
let lastLine = this._lastLine;
|
|
||||||
|
|
||||||
for (let x = 0; x < byteWidth; x++) {
|
|
||||||
let rawByte = rawData[1 + x];
|
|
||||||
let f4Up = lastLine ? lastLine[x] : 0;
|
|
||||||
let f4Left = x > xBiggerThan ? unfilteredLine[x - xComparison] : 0;
|
|
||||||
let f4UpLeft = x > xBiggerThan && lastLine ? lastLine[x - xComparison] : 0;
|
|
||||||
let f4Add = paethPredictor(f4Left, f4Up, f4UpLeft);
|
|
||||||
unfilteredLine[x] = rawByte + f4Add;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Filter.prototype._reverseFilterLine = function (rawData) {
|
|
||||||
let filter = rawData[0];
|
|
||||||
let unfilteredLine;
|
|
||||||
let currentImage = this._images[this._imageIndex];
|
|
||||||
let byteWidth = currentImage.byteWidth;
|
|
||||||
|
|
||||||
if (filter === 0) {
|
|
||||||
unfilteredLine = rawData.slice(1, byteWidth + 1);
|
|
||||||
} else {
|
|
||||||
unfilteredLine = Buffer.alloc(byteWidth);
|
|
||||||
|
|
||||||
switch (filter) {
|
|
||||||
case 1:
|
|
||||||
this._unFilterType1(rawData, unfilteredLine, byteWidth);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
this._unFilterType2(rawData, unfilteredLine, byteWidth);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
this._unFilterType3(rawData, unfilteredLine, byteWidth);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
this._unFilterType4(rawData, unfilteredLine, byteWidth);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("Unrecognised filter type - " + filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.write(unfilteredLine);
|
|
||||||
|
|
||||||
currentImage.lineIndex++;
|
|
||||||
if (currentImage.lineIndex >= currentImage.height) {
|
|
||||||
this._lastLine = null;
|
|
||||||
this._imageIndex++;
|
|
||||||
currentImage = this._images[this._imageIndex];
|
|
||||||
} else {
|
|
||||||
this._lastLine = unfilteredLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentImage) {
|
|
||||||
// read, using the byte width that may be from the new current image
|
|
||||||
this.read(currentImage.byteWidth + 1, this._reverseFilterLine.bind(this));
|
|
||||||
} else {
|
|
||||||
this._lastLine = null;
|
|
||||||
this.complete();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
314
lib/filter.js
Executable file
|
|
@ -0,0 +1,314 @@
|
||||||
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var util = require('util'),
|
||||||
|
zlib = require('zlib'),
|
||||||
|
ChunkStream = require('./chunkstream');
|
||||||
|
|
||||||
|
|
||||||
|
var Filter = module.exports = function(width, height, Bpp, data, options) {
|
||||||
|
ChunkStream.call(this);
|
||||||
|
|
||||||
|
this._width = width;
|
||||||
|
this._height = height;
|
||||||
|
this._Bpp = Bpp;
|
||||||
|
this._data = data;
|
||||||
|
this._options = options;
|
||||||
|
|
||||||
|
this._line = 0;
|
||||||
|
|
||||||
|
if (!('filterType' in options) || options.filterType == -1) {
|
||||||
|
options.filterType = [0, 1, 2, 3, 4];
|
||||||
|
} else if (typeof options.filterType == 'number') {
|
||||||
|
options.filterType = [options.filterType];
|
||||||
|
}
|
||||||
|
|
||||||
|
this._filters = {
|
||||||
|
0: this._filterNone.bind(this),
|
||||||
|
1: this._filterSub.bind(this),
|
||||||
|
2: this._filterUp.bind(this),
|
||||||
|
3: this._filterAvg.bind(this),
|
||||||
|
4: this._filterPaeth.bind(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.read(this._width * Bpp + 1, this._reverseFilterLine.bind(this));
|
||||||
|
};
|
||||||
|
util.inherits(Filter, ChunkStream);
|
||||||
|
|
||||||
|
|
||||||
|
var pixelBppMap = {
|
||||||
|
1: { // L
|
||||||
|
0: 0,
|
||||||
|
1: 0,
|
||||||
|
2: 0,
|
||||||
|
3: 0xff
|
||||||
|
},
|
||||||
|
2: { // LA
|
||||||
|
0: 0,
|
||||||
|
1: 0,
|
||||||
|
2: 0,
|
||||||
|
3: 1
|
||||||
|
},
|
||||||
|
3: { // RGB
|
||||||
|
0: 0,
|
||||||
|
1: 1,
|
||||||
|
2: 2,
|
||||||
|
3: 0xff
|
||||||
|
},
|
||||||
|
4: { // RGBA
|
||||||
|
0: 0,
|
||||||
|
1: 1,
|
||||||
|
2: 2,
|
||||||
|
3: 3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._reverseFilterLine = function(rawData) {
|
||||||
|
|
||||||
|
var pxData = this._data,
|
||||||
|
pxLineLength = this._width << 2,
|
||||||
|
pxRowPos = this._line * pxLineLength,
|
||||||
|
filter = rawData[0];
|
||||||
|
|
||||||
|
if (filter == 0) {
|
||||||
|
for (var x = 0; x < this._width; x++) {
|
||||||
|
var pxPos = pxRowPos + (x << 2),
|
||||||
|
rawPos = 1 + x * this._Bpp;
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var idx = pixelBppMap[this._Bpp][i];
|
||||||
|
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] : 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (filter == 1) {
|
||||||
|
for (var x = 0; x < this._width; x++) {
|
||||||
|
var pxPos = pxRowPos + (x << 2),
|
||||||
|
rawPos = 1 + x * this._Bpp;
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var idx = pixelBppMap[this._Bpp][i],
|
||||||
|
left = x > 0 ? pxData[pxPos + i - 4] : 0;
|
||||||
|
|
||||||
|
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + left : 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (filter == 2) {
|
||||||
|
for (var x = 0; x < this._width; x++) {
|
||||||
|
var pxPos = pxRowPos + (x << 2),
|
||||||
|
rawPos = 1 + x * this._Bpp;
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var idx = pixelBppMap[this._Bpp][i],
|
||||||
|
up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0;
|
||||||
|
|
||||||
|
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + up : 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (filter == 3) {
|
||||||
|
for (var x = 0; x < this._width; x++) {
|
||||||
|
var pxPos = pxRowPos + (x << 2),
|
||||||
|
rawPos = 1 + x * this._Bpp;
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var idx = pixelBppMap[this._Bpp][i],
|
||||||
|
left = x > 0 ? pxData[pxPos + i - 4] : 0,
|
||||||
|
up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0,
|
||||||
|
add = Math.floor((left + up) / 2);
|
||||||
|
|
||||||
|
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + add : 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (filter == 4) {
|
||||||
|
for (var x = 0; x < this._width; x++) {
|
||||||
|
var pxPos = pxRowPos + (x << 2),
|
||||||
|
rawPos = 1 + x * this._Bpp;
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
var idx = pixelBppMap[this._Bpp][i],
|
||||||
|
left = x > 0 ? pxData[pxPos + i - 4] : 0,
|
||||||
|
up = this._line > 0 ? pxData[pxPos - pxLineLength + i] : 0,
|
||||||
|
upLeft = x > 0 && this._line > 0
|
||||||
|
? pxData[pxPos - pxLineLength + i - 4] : 0,
|
||||||
|
add = PaethPredictor(left, up, upLeft);
|
||||||
|
|
||||||
|
pxData[pxPos + i] = idx != 0xff ? rawData[rawPos + idx] + add : 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this._line++;
|
||||||
|
|
||||||
|
if (this._line < this._height)
|
||||||
|
this.read(this._width * this._Bpp + 1, this._reverseFilterLine.bind(this));
|
||||||
|
else
|
||||||
|
this.emit('complete', this._data, this._width, this._height);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Filter.prototype.filter = function() {
|
||||||
|
|
||||||
|
var pxData = this._data,
|
||||||
|
rawData = new Buffer(((this._width << 2) + 1) * this._height);
|
||||||
|
|
||||||
|
for (var y = 0; y < this._height; y++) {
|
||||||
|
|
||||||
|
// find best filter for this line (with lowest sum of values)
|
||||||
|
var filterTypes = this._options.filterType,
|
||||||
|
min = Infinity,
|
||||||
|
sel = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < filterTypes.length; i++) {
|
||||||
|
var sum = this._filters[filterTypes[i]](pxData, y, null);
|
||||||
|
if (sum < min) {
|
||||||
|
sel = filterTypes[i];
|
||||||
|
min = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._filters[sel](pxData, y, rawData);
|
||||||
|
}
|
||||||
|
return rawData;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterNone = function(pxData, y, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = this._width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (!rawData) {
|
||||||
|
for (var x = 0; x < pxRowLength; x++)
|
||||||
|
sum += Math.abs(pxData[y * pxRowLength + x]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rawData[y * rawRowLength] = 0;
|
||||||
|
pxData.copy(rawData, rawRowLength * y + 1, pxRowLength * y, pxRowLength * (y + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterSub = function(pxData, y, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = this._width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 1;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - left;
|
||||||
|
|
||||||
|
if (!rawData) sum += Math.abs(val);
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterUp = function(pxData, y, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = this._width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 2;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - up;
|
||||||
|
|
||||||
|
if (!rawData) sum += Math.abs(val);
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterAvg = function(pxData, y, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = this._width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 3;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
|
||||||
|
up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - ((left + up) >> 1);
|
||||||
|
|
||||||
|
if (!rawData) sum += Math.abs(val);
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter.prototype._filterPaeth = function(pxData, y, rawData) {
|
||||||
|
|
||||||
|
var pxRowLength = this._width << 2,
|
||||||
|
rawRowLength = pxRowLength + 1,
|
||||||
|
sum = 0;
|
||||||
|
|
||||||
|
if (rawData)
|
||||||
|
rawData[y * rawRowLength] = 4;
|
||||||
|
|
||||||
|
for (var x = 0; x < pxRowLength; x++) {
|
||||||
|
|
||||||
|
var left = x >= 4 ? pxData[y * pxRowLength + x - 4] : 0,
|
||||||
|
up = y > 0 ? pxData[(y - 1) * pxRowLength + x] : 0,
|
||||||
|
upLeft = x >= 4 && y > 0 ? pxData[(y - 1) * pxRowLength + x - 4] : 0,
|
||||||
|
val = pxData[y * pxRowLength + x] - PaethPredictor(left, up, upLeft);
|
||||||
|
|
||||||
|
if (!rawData) sum += Math.abs(val);
|
||||||
|
else rawData[y * rawRowLength + 1 + x] = val;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var PaethPredictor = function(left, above, upLeft) {
|
||||||
|
|
||||||
|
var p = left + above - upLeft,
|
||||||
|
pLeft = Math.abs(p - left),
|
||||||
|
pAbove = Math.abs(p - above),
|
||||||
|
pUpLeft = Math.abs(p - upLeft);
|
||||||
|
|
||||||
|
if (pLeft <= pAbove && pLeft <= pUpLeft) return left;
|
||||||
|
else if (pAbove <= pUpLeft) return above;
|
||||||
|
else return upLeft;
|
||||||
|
};
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
function dePalette(indata, outdata, width, height, palette) {
|
|
||||||
let pxPos = 0;
|
|
||||||
// use values from palette
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
let color = palette[indata[pxPos]];
|
|
||||||
|
|
||||||
if (!color) {
|
|
||||||
throw new Error("index " + indata[pxPos] + " not in palette");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
outdata[pxPos + i] = color[i];
|
|
||||||
}
|
|
||||||
pxPos += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceTransparentColor(indata, outdata, width, height, transColor) {
|
|
||||||
let pxPos = 0;
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
let makeTrans = false;
|
|
||||||
|
|
||||||
if (transColor.length === 1) {
|
|
||||||
if (transColor[0] === indata[pxPos]) {
|
|
||||||
makeTrans = true;
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
transColor[0] === indata[pxPos] &&
|
|
||||||
transColor[1] === indata[pxPos + 1] &&
|
|
||||||
transColor[2] === indata[pxPos + 2]
|
|
||||||
) {
|
|
||||||
makeTrans = true;
|
|
||||||
}
|
|
||||||
if (makeTrans) {
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
outdata[pxPos + i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pxPos += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function scaleDepth(indata, outdata, width, height, depth) {
|
|
||||||
let maxOutSample = 255;
|
|
||||||
let maxInSample = Math.pow(2, depth) - 1;
|
|
||||||
let pxPos = 0;
|
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
outdata[pxPos + i] = Math.floor(
|
|
||||||
(indata[pxPos + i] * maxOutSample) / maxInSample + 0.5
|
|
||||||
);
|
|
||||||
}
|
|
||||||
pxPos += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (indata, imageData) {
|
|
||||||
let depth = imageData.depth;
|
|
||||||
let width = imageData.width;
|
|
||||||
let height = imageData.height;
|
|
||||||
let colorType = imageData.colorType;
|
|
||||||
let transColor = imageData.transColor;
|
|
||||||
let palette = imageData.palette;
|
|
||||||
|
|
||||||
let outdata = indata; // only different for 16 bits
|
|
||||||
|
|
||||||
if (colorType === 3) {
|
|
||||||
// paletted
|
|
||||||
dePalette(indata, outdata, width, height, palette);
|
|
||||||
} else {
|
|
||||||
if (transColor) {
|
|
||||||
replaceTransparentColor(indata, outdata, width, height, transColor);
|
|
||||||
}
|
|
||||||
// if it needs scaling
|
|
||||||
if (depth !== 8) {
|
|
||||||
// if we need to change the buffer size
|
|
||||||
if (depth === 16) {
|
|
||||||
outdata = Buffer.alloc(width * height * 4);
|
|
||||||
}
|
|
||||||
scaleDepth(indata, outdata, width, height, depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outdata;
|
|
||||||
};
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
// Adam 7
|
|
||||||
// 0 1 2 3 4 5 6 7
|
|
||||||
// 0 x 6 4 6 x 6 4 6
|
|
||||||
// 1 7 7 7 7 7 7 7 7
|
|
||||||
// 2 5 6 5 6 5 6 5 6
|
|
||||||
// 3 7 7 7 7 7 7 7 7
|
|
||||||
// 4 3 6 4 6 3 6 4 6
|
|
||||||
// 5 7 7 7 7 7 7 7 7
|
|
||||||
// 6 5 6 5 6 5 6 5 6
|
|
||||||
// 7 7 7 7 7 7 7 7 7
|
|
||||||
|
|
||||||
let imagePasses = [
|
|
||||||
{
|
|
||||||
// pass 1 - 1px
|
|
||||||
x: [0],
|
|
||||||
y: [0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// pass 2 - 1px
|
|
||||||
x: [4],
|
|
||||||
y: [0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// pass 3 - 2px
|
|
||||||
x: [0, 4],
|
|
||||||
y: [4],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// pass 4 - 4px
|
|
||||||
x: [2, 6],
|
|
||||||
y: [0, 4],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// pass 5 - 8px
|
|
||||||
x: [0, 2, 4, 6],
|
|
||||||
y: [2, 6],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// pass 6 - 16px
|
|
||||||
x: [1, 3, 5, 7],
|
|
||||||
y: [0, 2, 4, 6],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// pass 7 - 32px
|
|
||||||
x: [0, 1, 2, 3, 4, 5, 6, 7],
|
|
||||||
y: [1, 3, 5, 7],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export var getImagePasses = function (width, height) {
|
|
||||||
let images = [];
|
|
||||||
let xLeftOver = width % 8;
|
|
||||||
let yLeftOver = height % 8;
|
|
||||||
let xRepeats = (width - xLeftOver) / 8;
|
|
||||||
let yRepeats = (height - yLeftOver) / 8;
|
|
||||||
for (let i = 0; i < imagePasses.length; i++) {
|
|
||||||
let pass = imagePasses[i];
|
|
||||||
let passWidth = xRepeats * pass.x.length;
|
|
||||||
let passHeight = yRepeats * pass.y.length;
|
|
||||||
for (let j = 0; j < pass.x.length; j++) {
|
|
||||||
if (pass.x[j] < xLeftOver) {
|
|
||||||
passWidth++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let j = 0; j < pass.y.length; j++) {
|
|
||||||
if (pass.y[j] < yLeftOver) {
|
|
||||||
passHeight++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (passWidth > 0 && passHeight > 0) {
|
|
||||||
images.push({ width: passWidth, height: passHeight, index: i });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return images;
|
|
||||||
};
|
|
||||||
|
|
||||||
export var getInterlaceIterator = function (width) {
|
|
||||||
return function (x, y, pass) {
|
|
||||||
let outerXLeftOver = x % imagePasses[pass].x.length;
|
|
||||||
let outerX =
|
|
||||||
((x - outerXLeftOver) / imagePasses[pass].x.length) * 8 +
|
|
||||||
imagePasses[pass].x[outerXLeftOver];
|
|
||||||
let outerYLeftOver = y % imagePasses[pass].y.length;
|
|
||||||
let outerY =
|
|
||||||
((y - outerYLeftOver) / imagePasses[pass].y.length) * 8 +
|
|
||||||
imagePasses[pass].y[outerYLeftOver];
|
|
||||||
return outerX * 4 + outerY * width * 4;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getImagePasses,
|
|
||||||
getInterlaceIterator
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
let util = require("util");
|
|
||||||
let Stream = require("stream");
|
|
||||||
|
|
||||||
import constants from "./constants.js";
|
|
||||||
import Packer from "./packer.js";
|
|
||||||
|
|
||||||
let PackerAsync = function (opt) {
|
|
||||||
Stream.call(this);
|
|
||||||
|
|
||||||
let options = opt || {};
|
|
||||||
|
|
||||||
this._packer = new Packer(options);
|
|
||||||
this._deflate = this._packer.createDeflate();
|
|
||||||
|
|
||||||
this.readable = true;
|
|
||||||
};
|
|
||||||
util.inherits(PackerAsync, Stream);
|
|
||||||
|
|
||||||
export default PackerAsync;
|
|
||||||
|
|
||||||
PackerAsync.prototype.pack = function (data, width, height, gamma) {
|
|
||||||
// Signature
|
|
||||||
this.emit("data", Buffer.from(constants.PNG_SIGNATURE));
|
|
||||||
this.emit("data", this._packer.packIHDR(width, height));
|
|
||||||
|
|
||||||
if (gamma) {
|
|
||||||
this.emit("data", this._packer.packGAMA(gamma));
|
|
||||||
}
|
|
||||||
|
|
||||||
let filteredData = this._packer.filterData(data, width, height);
|
|
||||||
|
|
||||||
// compress it
|
|
||||||
this._deflate.on("error", this.emit.bind(this, "error"));
|
|
||||||
|
|
||||||
this._deflate.on(
|
|
||||||
"data",
|
|
||||||
function (compressedData) {
|
|
||||||
this.emit("data", this._packer.packIDAT(compressedData));
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
this._deflate.on(
|
|
||||||
"end",
|
|
||||||
function () {
|
|
||||||
this.emit("data", this._packer.packIEND());
|
|
||||||
this.emit("end");
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
this._deflate.end(filteredData);
|
|
||||||
};
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import constants from "./constants.js";
|
|
||||||
import Packer from "./packer.js";
|
|
||||||
|
|
||||||
let hasSyncZlib = true;
|
|
||||||
let zlib = require("zlib");
|
|
||||||
|
|
||||||
export default function (metaData, opt) {
|
|
||||||
if (!hasSyncZlib) {
|
|
||||||
throw new Error(
|
|
||||||
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = opt || {};
|
|
||||||
|
|
||||||
let packer = new Packer(options);
|
|
||||||
|
|
||||||
let chunks = [];
|
|
||||||
|
|
||||||
// Signature
|
|
||||||
chunks.push(Buffer.from(constants.PNG_SIGNATURE));
|
|
||||||
|
|
||||||
// Header
|
|
||||||
chunks.push(packer.packIHDR(metaData.width, metaData.height));
|
|
||||||
|
|
||||||
if (metaData.gamma) {
|
|
||||||
chunks.push(packer.packGAMA(metaData.gamma));
|
|
||||||
}
|
|
||||||
|
|
||||||
let filteredData = packer.filterData(
|
|
||||||
metaData.data,
|
|
||||||
metaData.width,
|
|
||||||
metaData.height
|
|
||||||
);
|
|
||||||
|
|
||||||
// compress it
|
|
||||||
let compressedData = zlib.deflateSync(
|
|
||||||
filteredData,
|
|
||||||
packer.getDeflateOptions()
|
|
||||||
);
|
|
||||||
filteredData = null;
|
|
||||||
|
|
||||||
if (!compressedData || !compressedData.length) {
|
|
||||||
throw new Error("bad png - invalid compressed data response");
|
|
||||||
}
|
|
||||||
chunks.push(packer.packIDAT(compressedData));
|
|
||||||
|
|
||||||
// End
|
|
||||||
chunks.push(packer.packIEND());
|
|
||||||
|
|
||||||
return Buffer.concat(chunks);
|
|
||||||
};
|
|
||||||
212
lib/packer.js
Normal file → Executable file
|
|
@ -1,130 +1,110 @@
|
||||||
import constants from "./constants.js";
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
import CrcStream from "./crc.js";
|
//
|
||||||
import bitPacker from "./bitpacker.js";
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
import filter from "./filter-pack.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.
|
||||||
|
|
||||||
let zlib = require("zlib");
|
'use strict';
|
||||||
|
|
||||||
let Packer = function (options) {
|
|
||||||
this._options = options;
|
|
||||||
|
|
||||||
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
|
var util = require('util'),
|
||||||
options.deflateLevel =
|
Stream = require('stream'),
|
||||||
options.deflateLevel != null ? options.deflateLevel : 9;
|
zlib = require('zlib'),
|
||||||
options.deflateStrategy =
|
Filter = require('./filter'),
|
||||||
options.deflateStrategy != null ? options.deflateStrategy : 3;
|
CrcStream = require('./crc'),
|
||||||
options.inputHasAlpha =
|
constants = require('./constants');
|
||||||
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) {
|
||||||
constants.COLORTYPE_GRAYSCALE,
|
Stream.call(this);
|
||||||
constants.COLORTYPE_COLOR,
|
|
||||||
constants.COLORTYPE_COLOR_ALPHA,
|
this._options = options;
|
||||||
constants.COLORTYPE_ALPHA,
|
|
||||||
].indexOf(options.colorType) === -1
|
options.deflateChunkSize = options.deflateChunkSize || 32 * 1024;
|
||||||
) {
|
options.deflateLevel = options.deflateLevel || 9;
|
||||||
throw new Error(
|
options.deflateStrategy = options.deflateStrategy || 3;
|
||||||
"option color type:" + options.colorType + " is not supported at present"
|
|
||||||
);
|
this.readable = true;
|
||||||
}
|
};
|
||||||
if (
|
util.inherits(Packer, Stream);
|
||||||
[
|
|
||||||
constants.COLORTYPE_GRAYSCALE,
|
|
||||||
constants.COLORTYPE_COLOR,
|
Packer.prototype.pack = function(data, width, height) {
|
||||||
constants.COLORTYPE_COLOR_ALPHA,
|
|
||||||
constants.COLORTYPE_ALPHA,
|
// Signature
|
||||||
].indexOf(options.inputColorType) === -1
|
this.emit('data', new Buffer(constants.PNG_SIGNATURE));
|
||||||
) {
|
this.emit('data', this._packIHDR(width, height));
|
||||||
throw new Error(
|
|
||||||
"option input color type:" +
|
// filter pixel data
|
||||||
options.inputColorType +
|
var filter = new Filter(width, height, 4, data, this._options);
|
||||||
" is not supported at present"
|
var data = filter.filter();
|
||||||
);
|
|
||||||
}
|
// compress it
|
||||||
if (options.bitDepth !== 8 && options.bitDepth !== 16) {
|
var deflate = zlib.createDeflate({
|
||||||
throw new Error(
|
chunkSize: this._options.deflateChunkSize,
|
||||||
"option bit depth:" + options.bitDepth + " is not supported at present"
|
level: this._options.deflateLevel,
|
||||||
);
|
strategy: this._options.deflateStrategy
|
||||||
}
|
});
|
||||||
|
deflate.on('error', this.emit.bind(this, 'error'));
|
||||||
|
|
||||||
|
deflate.on('data', function(data) {
|
||||||
|
this.emit('data', this._packIDAT(data));
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
deflate.on('end', function() {
|
||||||
|
this.emit('data', this._packIEND());
|
||||||
|
this.emit('end');
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
deflate.end(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Packer;
|
Packer.prototype._packChunk = function(type, data) {
|
||||||
|
|
||||||
Packer.prototype.getDeflateOptions = function () {
|
var len = (data ? data.length : 0),
|
||||||
return {
|
buf = new Buffer(len + 12);
|
||||||
chunkSize: this._options.deflateChunkSize,
|
|
||||||
level: this._options.deflateLevel,
|
buf.writeUInt32BE(len, 0);
|
||||||
strategy: this._options.deflateStrategy,
|
buf.writeUInt32BE(type, 4);
|
||||||
};
|
|
||||||
|
if (data) data.copy(buf, 8);
|
||||||
|
|
||||||
|
buf.writeInt32BE(CrcStream.crc32(buf.slice(4, buf.length - 4)), buf.length - 4);
|
||||||
|
return buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
Packer.prototype.createDeflate = function () {
|
Packer.prototype._packIHDR = function(width, height) {
|
||||||
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.filterData = function (data, width, height) {
|
Packer.prototype._packIDAT = function(data) {
|
||||||
// convert to correct format for filtering (e.g. right bpp and bit depth)
|
return this._packChunk(constants.TYPE_IDAT, data);
|
||||||
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._packChunk = function (type, data) {
|
Packer.prototype._packIEND = function() {
|
||||||
let len = data ? data.length : 0;
|
return this._packChunk(constants.TYPE_IEND, null);
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
export default function paethPredictor(left, above, upLeft) {
|
|
||||||
let paeth = left + above - upLeft;
|
|
||||||
let pLeft = Math.abs(paeth - left);
|
|
||||||
let pAbove = Math.abs(paeth - above);
|
|
||||||
let pUpLeft = Math.abs(paeth - upLeft);
|
|
||||||
|
|
||||||
if (pLeft <= pAbove && pLeft <= pUpLeft) {
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
if (pAbove <= pUpLeft) {
|
|
||||||
return above;
|
|
||||||
}
|
|
||||||
return upLeft;
|
|
||||||
};
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
import ChunkStream from "./chunkstream.js";
|
|
||||||
import FilterAsync from "./filter-parse-async.js";
|
|
||||||
import Parser from "./parser.js";
|
|
||||||
import bitmapper from "./bitmapper.js";
|
|
||||||
import formatNormaliser from "./format-normaliser.js";
|
|
||||||
|
|
||||||
let util = require("util");
|
|
||||||
let zlib = require("zlib");
|
|
||||||
|
|
||||||
let ParserAsync = function (options) {
|
|
||||||
ChunkStream.call(this);
|
|
||||||
|
|
||||||
this._parser = new Parser(options, {
|
|
||||||
read: this.read.bind(this),
|
|
||||||
error: this._handleError.bind(this),
|
|
||||||
metadata: this._handleMetaData.bind(this),
|
|
||||||
gamma: this.emit.bind(this, "gamma"),
|
|
||||||
palette: this._handlePalette.bind(this),
|
|
||||||
transColor: this._handleTransColor.bind(this),
|
|
||||||
finished: this._finished.bind(this),
|
|
||||||
inflateData: this._inflateData.bind(this),
|
|
||||||
simpleTransparency: this._simpleTransparency.bind(this),
|
|
||||||
headersFinished: this._headersFinished.bind(this),
|
|
||||||
});
|
|
||||||
this._options = options;
|
|
||||||
this.writable = true;
|
|
||||||
|
|
||||||
this._parser.start();
|
|
||||||
};
|
|
||||||
util.inherits(ParserAsync, ChunkStream);
|
|
||||||
|
|
||||||
export default ParserAsync;
|
|
||||||
|
|
||||||
ParserAsync.prototype._handleError = function (err) {
|
|
||||||
this.emit("error", err);
|
|
||||||
|
|
||||||
this.writable = false;
|
|
||||||
|
|
||||||
this.destroy();
|
|
||||||
|
|
||||||
if (this._inflate && this._inflate.destroy) {
|
|
||||||
this._inflate.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._filter) {
|
|
||||||
this._filter.destroy();
|
|
||||||
// For backward compatibility with Node 7 and below.
|
|
||||||
// Suppress errors due to _inflate calling write() even after
|
|
||||||
// it's destroy()'ed.
|
|
||||||
this._filter.on("error", function () {});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.errord = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._inflateData = function (data) {
|
|
||||||
if (!this._inflate) {
|
|
||||||
if (this._bitmapInfo.interlace) {
|
|
||||||
this._inflate = zlib.createInflate();
|
|
||||||
|
|
||||||
this._inflate.on("error", this.emit.bind(this, "error"));
|
|
||||||
this._filter.on("complete", this._complete.bind(this));
|
|
||||||
|
|
||||||
this._inflate.pipe(this._filter);
|
|
||||||
} else {
|
|
||||||
let rowSize =
|
|
||||||
((this._bitmapInfo.width *
|
|
||||||
this._bitmapInfo.bpp *
|
|
||||||
this._bitmapInfo.depth +
|
|
||||||
7) >>
|
|
||||||
3) +
|
|
||||||
1;
|
|
||||||
let imageSize = rowSize * this._bitmapInfo.height;
|
|
||||||
let chunkSize = Math.max(imageSize, zlib.Z_MIN_CHUNK);
|
|
||||||
|
|
||||||
this._inflate = zlib.createInflate({ chunkSize: chunkSize });
|
|
||||||
let leftToInflate = imageSize;
|
|
||||||
|
|
||||||
let emitError = this.emit.bind(this, "error");
|
|
||||||
this._inflate.on("error", function (err) {
|
|
||||||
if (!leftToInflate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emitError(err);
|
|
||||||
});
|
|
||||||
this._filter.on("complete", this._complete.bind(this));
|
|
||||||
|
|
||||||
let filterWrite = this._filter.write.bind(this._filter);
|
|
||||||
this._inflate.on("data", function (chunk) {
|
|
||||||
if (!leftToInflate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunk.length > leftToInflate) {
|
|
||||||
chunk = chunk.slice(0, leftToInflate);
|
|
||||||
}
|
|
||||||
|
|
||||||
leftToInflate -= chunk.length;
|
|
||||||
|
|
||||||
filterWrite(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._inflate.on("end", this._filter.end.bind(this._filter));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._inflate.write(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._handleMetaData = function (metaData) {
|
|
||||||
this._metaData = metaData;
|
|
||||||
this._bitmapInfo = Object.create(metaData);
|
|
||||||
|
|
||||||
this._filter = new FilterAsync(this._bitmapInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._handleTransColor = function (transColor) {
|
|
||||||
this._bitmapInfo.transColor = transColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._handlePalette = function (palette) {
|
|
||||||
this._bitmapInfo.palette = palette;
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._simpleTransparency = function () {
|
|
||||||
this._metaData.alpha = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._headersFinished = function () {
|
|
||||||
// Up until this point, we don't know if we have a tRNS chunk (alpha)
|
|
||||||
// so we can't emit metadata any earlier
|
|
||||||
this.emit("metadata", this._metaData);
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._finished = function () {
|
|
||||||
if (this.errord) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._inflate) {
|
|
||||||
this.emit("error", "No Inflate block");
|
|
||||||
} else {
|
|
||||||
// no more data to inflate
|
|
||||||
this._inflate.end();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ParserAsync.prototype._complete = function (filteredData) {
|
|
||||||
if (this.errord) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let normalisedBitmapData;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let bitmapData = bitmapper(filteredData, this._bitmapInfo);
|
|
||||||
|
|
||||||
normalisedBitmapData = formatNormaliser(bitmapData, this._bitmapInfo);
|
|
||||||
bitmapData = null;
|
|
||||||
} catch (ex) {
|
|
||||||
this._handleError(ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("parsed", normalisedBitmapData);
|
|
||||||
};
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
import inflateSync from "./sync-inflate.js";
|
|
||||||
import SyncReader from "./sync-reader.js";
|
|
||||||
import FilterSync from "./filter-parse-sync.js";
|
|
||||||
import bitmapper from "./bitmapper.js";
|
|
||||||
import formatNormaliser from "./format-normaliser.js";
|
|
||||||
import Parser from "./parser.js";
|
|
||||||
|
|
||||||
let hasSyncZlib = true;
|
|
||||||
let zlib = require("zlib");
|
|
||||||
if (!zlib.deflateSync) {
|
|
||||||
hasSyncZlib = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (buffer, options) {
|
|
||||||
if (!hasSyncZlib) {
|
|
||||||
throw new Error(
|
|
||||||
"To use the sync capability of this library in old node versions, please pin pngjs to v2.3.0"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let err;
|
|
||||||
function handleError(_err_) {
|
|
||||||
err = _err_;
|
|
||||||
}
|
|
||||||
|
|
||||||
let metaData;
|
|
||||||
function handleMetaData(_metaData_) {
|
|
||||||
metaData = _metaData_;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTransColor(transColor) {
|
|
||||||
metaData.transColor = transColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handlePalette(palette) {
|
|
||||||
metaData.palette = palette;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSimpleTransparency() {
|
|
||||||
metaData.alpha = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let gamma;
|
|
||||||
function handleGamma(_gamma_) {
|
|
||||||
gamma = _gamma_;
|
|
||||||
}
|
|
||||||
|
|
||||||
let inflateDataList = [];
|
|
||||||
function handleInflateData(inflatedData) {
|
|
||||||
inflateDataList.push(inflatedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
let reader = new SyncReader(buffer);
|
|
||||||
|
|
||||||
let parser = new Parser(options, {
|
|
||||||
read: reader.read.bind(reader),
|
|
||||||
error: handleError,
|
|
||||||
metadata: handleMetaData,
|
|
||||||
gamma: handleGamma,
|
|
||||||
palette: handlePalette,
|
|
||||||
transColor: handleTransColor,
|
|
||||||
inflateData: handleInflateData,
|
|
||||||
simpleTransparency: handleSimpleTransparency,
|
|
||||||
});
|
|
||||||
|
|
||||||
parser.start();
|
|
||||||
reader.process();
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
//join together the inflate datas
|
|
||||||
let inflateData = Buffer.concat(inflateDataList);
|
|
||||||
inflateDataList.length = 0;
|
|
||||||
|
|
||||||
let inflatedData;
|
|
||||||
if (metaData.interlace) {
|
|
||||||
inflatedData = zlib.inflateSync(inflateData);
|
|
||||||
} else {
|
|
||||||
let rowSize =
|
|
||||||
((metaData.width * metaData.bpp * metaData.depth + 7) >> 3) + 1;
|
|
||||||
let imageSize = rowSize * metaData.height;
|
|
||||||
inflatedData = inflateSync(inflateData, {
|
|
||||||
chunkSize: imageSize,
|
|
||||||
maxLength: imageSize,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
inflateData = null;
|
|
||||||
|
|
||||||
if (!inflatedData || !inflatedData.length) {
|
|
||||||
throw new Error("bad png - invalid inflate data response");
|
|
||||||
}
|
|
||||||
|
|
||||||
let unfilteredData = FilterSync.process(inflatedData, metaData);
|
|
||||||
inflateData = null;
|
|
||||||
|
|
||||||
let bitmapData = bitmapper.dataToBitMap(unfilteredData, metaData);
|
|
||||||
unfilteredData = null;
|
|
||||||
|
|
||||||
let normalisedBitmapData = formatNormaliser(bitmapData, metaData);
|
|
||||||
|
|
||||||
metaData.data = normalisedBitmapData;
|
|
||||||
metaData.gamma = gamma || 0;
|
|
||||||
|
|
||||||
return metaData;
|
|
||||||
};
|
|
||||||
575
lib/parser.js
Normal file → Executable file
|
|
@ -1,290 +1,359 @@
|
||||||
"use strict";
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
let constants = require("./constants");
|
'use strict';
|
||||||
let CrcCalculator = require("./crc");
|
|
||||||
|
|
||||||
let Parser = (module.exports = function (options, dependencies) {
|
|
||||||
this._options = options;
|
|
||||||
options.checkCRC = options.checkCRC !== false;
|
|
||||||
|
|
||||||
this._hasIHDR = false;
|
var util = require('util'),
|
||||||
this._hasIEND = false;
|
zlib = require('zlib'),
|
||||||
this._emittedHeadersFinished = false;
|
CrcStream = require('./crc'),
|
||||||
|
ChunkStream = require('./chunkstream'),
|
||||||
|
constants = require('./constants'),
|
||||||
|
Filter = require('./filter');
|
||||||
|
|
||||||
// input flags/metadata
|
|
||||||
this._palette = [];
|
|
||||||
this._colorType = 0;
|
|
||||||
|
|
||||||
this._chunks = {};
|
var Parser = module.exports = function(options) {
|
||||||
this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
|
ChunkStream.call(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.read = dependencies.read;
|
this._options = options;
|
||||||
this.error = dependencies.error;
|
options.checkCRC = options.checkCRC !== false;
|
||||||
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 () {};
|
|
||||||
});
|
|
||||||
|
|
||||||
Parser.prototype.start = function () {
|
this._hasIHDR = false;
|
||||||
this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
|
this._hasIEND = false;
|
||||||
|
|
||||||
|
this._inflate = null;
|
||||||
|
this._filter = null;
|
||||||
|
this._crc = null;
|
||||||
|
|
||||||
|
// input flags/metadata
|
||||||
|
this._palette = [];
|
||||||
|
this._colorType = 0;
|
||||||
|
|
||||||
|
this._chunks = {};
|
||||||
|
this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
|
||||||
|
this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
|
||||||
|
this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
|
||||||
|
this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
|
||||||
|
this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
|
||||||
|
this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
|
||||||
|
|
||||||
|
this.writable = true;
|
||||||
|
|
||||||
|
this.on('error', this._handleError.bind(this));
|
||||||
|
this._handleSignature();
|
||||||
|
};
|
||||||
|
util.inherits(Parser, ChunkStream);
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._handleError = function() {
|
||||||
|
|
||||||
|
this.writable = false;
|
||||||
|
|
||||||
|
this.destroy();
|
||||||
|
|
||||||
|
if (this._inflate)
|
||||||
|
this._inflate.destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype._parseSignature = function (data) {
|
Parser.prototype._handleSignature = function() {
|
||||||
let signature = constants.PNG_SIGNATURE;
|
this.read(constants.PNG_SIGNATURE.length,
|
||||||
|
this._parseSignature.bind(this)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < signature.length; i++) {
|
Parser.prototype._parseSignature = function(data) {
|
||||||
if (data[i] !== signature[i]) {
|
|
||||||
this.error(new Error("Invalid file signature"));
|
var signature = constants.PNG_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._handleIHDR = function (length) {
|
Parser.prototype._parseChunkBegin = function(data) {
|
||||||
this.read(length, this._parseIHDR.bind(this));
|
|
||||||
};
|
|
||||||
Parser.prototype._parseIHDR = function (data) {
|
|
||||||
this._crc.write(data);
|
|
||||||
|
|
||||||
let width = data.readUInt32BE(0);
|
// chunk content length
|
||||||
let height = data.readUInt32BE(4);
|
var length = data.readUInt32BE(0);
|
||||||
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];
|
|
||||||
|
|
||||||
// console.log(' width', width, 'height', height,
|
// chunk type
|
||||||
// 'depth', depth, 'colorType', colorType,
|
var type = data.readUInt32BE(4),
|
||||||
// 'compr', compr, 'filter', filter, 'interlace', interlace
|
name = '';
|
||||||
// );
|
for (var i = 4; i < 8; i++)
|
||||||
|
name += String.fromCharCode(data[i]);
|
||||||
|
|
||||||
if (
|
// console.log('chunk ', name, length);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._colorType = colorType;
|
// chunk flags
|
||||||
|
var ancillary = !!(data[4] & 0x20), // or critical
|
||||||
|
priv = !!(data[5] & 0x20), // or public
|
||||||
|
safeToCopy = !!(data[7] & 0x20); // or unsafe
|
||||||
|
|
||||||
let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
|
if (!this._hasIHDR && type != constants.TYPE_IHDR) {
|
||||||
|
this.emit('error', new Error('Expected IHDR on beggining'));
|
||||||
this._hasIHDR = true;
|
return;
|
||||||
|
|
||||||
this.metadata({
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
depth: depth,
|
|
||||||
interlace: Boolean(interlace),
|
|
||||||
palette: Boolean(colorType & constants.COLORTYPE_PALETTE),
|
|
||||||
color: Boolean(colorType & constants.COLORTYPE_COLOR),
|
|
||||||
alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),
|
|
||||||
bpp: bpp,
|
|
||||||
colorType: colorType,
|
|
||||||
});
|
|
||||||
|
|
||||||
this._handleChunkEnd();
|
|
||||||
};
|
|
||||||
|
|
||||||
Parser.prototype._handlePLTE = function (length) {
|
|
||||||
this.read(length, this._parsePLTE.bind(this));
|
|
||||||
};
|
|
||||||
Parser.prototype._parsePLTE = function (data) {
|
|
||||||
this._crc.write(data);
|
|
||||||
|
|
||||||
let entries = Math.floor(data.length / 3);
|
|
||||||
// console.log('Palette:', entries);
|
|
||||||
|
|
||||||
for (let i = 0; i < entries; i++) {
|
|
||||||
this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.palette(this._palette);
|
|
||||||
|
|
||||||
this._handleChunkEnd();
|
|
||||||
};
|
|
||||||
|
|
||||||
Parser.prototype._handleTRNS = function (length) {
|
|
||||||
this.simpleTransparency();
|
|
||||||
this.read(length, this._parseTRNS.bind(this));
|
|
||||||
};
|
|
||||||
Parser.prototype._parseTRNS = function (data) {
|
|
||||||
this._crc.write(data);
|
|
||||||
|
|
||||||
// palette
|
|
||||||
if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {
|
|
||||||
if (this._palette.length === 0) {
|
|
||||||
this.error(new Error("Transparency chunk must be after palette"));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (data.length > this._palette.length) {
|
|
||||||
this.error(new Error("More transparent colors than palette size"));
|
this._crc = new CrcStream();
|
||||||
return;
|
this._crc.write(new Buffer(name));
|
||||||
|
|
||||||
|
if (this._chunks[type]) {
|
||||||
|
return this._chunks[type](length);
|
||||||
|
|
||||||
|
} else if (!ancillary) {
|
||||||
|
this.emit('error', new Error('Unsupported critical chunk type ' + name));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.read(length + 4, this._skipChunk.bind(this));
|
||||||
}
|
}
|
||||||
for (let i = 0; i < data.length; i++) {
|
};
|
||||||
this._palette[i][3] = data[i];
|
|
||||||
|
Parser.prototype._skipChunk = function(data) {
|
||||||
|
this.read(8, this._parseChunkBegin.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleChunkEnd = function() {
|
||||||
|
this.read(4, this._parseChunkEnd.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._parseChunkEnd = function(data) {
|
||||||
|
|
||||||
|
var fileCrc = data.readInt32BE(0),
|
||||||
|
calcCrc = this._crc.crc32();
|
||||||
|
|
||||||
|
// check CRC
|
||||||
|
if (this._options.checkCRC && calcCrc != fileCrc) {
|
||||||
|
this.emit('error', new Error('Crc error'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
this.palette(this._palette);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for colorType 0 (grayscale) and 2 (rgb)
|
if (this._hasIEND) {
|
||||||
// there might be one gray/color defined as transparent
|
this.destroySoon();
|
||||||
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();
|
} else {
|
||||||
|
this.read(8, this._parseChunkBegin.bind(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Parser.prototype._handleGAMA = function (length) {
|
|
||||||
this.read(length, this._parseGAMA.bind(this));
|
Parser.prototype._handleIHDR = function(length) {
|
||||||
|
this.read(length, this._parseIHDR.bind(this));
|
||||||
};
|
};
|
||||||
Parser.prototype._parseGAMA = function (data) {
|
Parser.prototype._parseIHDR = function(data) {
|
||||||
this._crc.write(data);
|
|
||||||
this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);
|
|
||||||
|
|
||||||
this._handleChunkEnd();
|
this._crc.write(data);
|
||||||
};
|
|
||||||
|
|
||||||
Parser.prototype._handleIDAT = function (length) {
|
var width = data.readUInt32BE(0),
|
||||||
if (!this._emittedHeadersFinished) {
|
height = data.readUInt32BE(4),
|
||||||
this._emittedHeadersFinished = true;
|
depth = data[8],
|
||||||
this.headersFinished();
|
colorType = data[9], // bits: 1 palette, 2 color, 4 alpha
|
||||||
}
|
compr = data[10],
|
||||||
this.read(-length, this._parseIDAT.bind(this, length));
|
filter = data[11],
|
||||||
};
|
interlace = data[12];
|
||||||
Parser.prototype._parseIDAT = function (length, data) {
|
|
||||||
this._crc.write(data);
|
|
||||||
|
|
||||||
if (
|
// console.log(' width', width, 'height', height,
|
||||||
this._colorType === constants.COLORTYPE_PALETTE_COLOR &&
|
// 'depth', depth, 'colorType', colorType,
|
||||||
this._palette.length === 0
|
// 'compr', compr, 'filter', filter, 'interlace', interlace
|
||||||
) {
|
// );
|
||||||
throw new Error("Expected palette not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inflateData(data);
|
if (depth != 8) {
|
||||||
let leftOverLength = length - data.length;
|
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) {
|
|
||||||
this.read(length, this._parseIEND.bind(this));
|
|
||||||
};
|
|
||||||
Parser.prototype._parseIEND = function (data) {
|
|
||||||
this._crc.write(data);
|
|
||||||
|
|
||||||
this._hasIEND = true;
|
Parser.prototype._handlePLTE = function(length) {
|
||||||
this._handleChunkEnd();
|
this.read(length, this._parsePLTE.bind(this));
|
||||||
|
};
|
||||||
if (this.finished) {
|
Parser.prototype._parsePLTE = function(data) {
|
||||||
this.finished();
|
|
||||||
}
|
this._crc.write(data);
|
||||||
|
|
||||||
|
var entries = Math.floor(data.length / 3);
|
||||||
|
// console.log('Palette:', entries);
|
||||||
|
|
||||||
|
for (var i = 0; i < entries; i++) {
|
||||||
|
this._palette.push([
|
||||||
|
data.readUInt8(i * 3),
|
||||||
|
data.readUInt8(i * 3 + 1),
|
||||||
|
data.readUInt8(i * 3 + 2 ),
|
||||||
|
0xff
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleTRNS = function(length) {
|
||||||
|
this.read(length, this._parseTRNS.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseTRNS = function(data) {
|
||||||
|
|
||||||
|
this._crc.write(data);
|
||||||
|
|
||||||
|
// palette
|
||||||
|
if (this._colorType == 3) {
|
||||||
|
if (this._palette.length == 0) {
|
||||||
|
this.emit('error', new Error('Transparency chunk must be after palette'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.length > this._palette.length) {
|
||||||
|
this.emit('error', new Error('More transparent colors than palette size'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < this._palette.length; i++) {
|
||||||
|
this._palette[i][3] = i < data.length ? data.readUInt8(i) : 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for colorType 0 (grayscale) and 2 (rgb)
|
||||||
|
// there might be one gray/color defined as transparent
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleGAMA = function(length) {
|
||||||
|
this.read(length, this._parseGAMA.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseGAMA = function(data) {
|
||||||
|
|
||||||
|
this._crc.write(data);
|
||||||
|
this.emit('gamma', data.readUInt32BE(0) / 100000);
|
||||||
|
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._handleIDAT = function(length) {
|
||||||
|
this.read(-length, this._parseIDAT.bind(this, length));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseIDAT = function(length, data) {
|
||||||
|
|
||||||
|
this._crc.write(data);
|
||||||
|
|
||||||
|
if (this._colorType == 3 && this._palette.length == 0)
|
||||||
|
throw new Error('Expected palette not found');
|
||||||
|
|
||||||
|
if (!this._inflate) {
|
||||||
|
this._inflate = zlib.createInflate();
|
||||||
|
|
||||||
|
this._inflate.on('error', this.emit.bind(this, 'error'));
|
||||||
|
this._filter.on('complete', this._reverseFiltered.bind(this));
|
||||||
|
|
||||||
|
this._inflate.pipe(this._filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._inflate.write(data);
|
||||||
|
length -= data.length;
|
||||||
|
|
||||||
|
if (length > 0)
|
||||||
|
this._handleIDAT(length);
|
||||||
|
else
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Parser.prototype._handleIEND = function(length) {
|
||||||
|
this.read(length, this._parseIEND.bind(this));
|
||||||
|
};
|
||||||
|
Parser.prototype._parseIEND = function(data) {
|
||||||
|
|
||||||
|
this._crc.write(data);
|
||||||
|
|
||||||
|
// no more data to inflate
|
||||||
|
this._inflate.end();
|
||||||
|
|
||||||
|
this._hasIEND = true;
|
||||||
|
this._handleChunkEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var colorTypeToBppMap = {
|
||||||
|
0: 1,
|
||||||
|
2: 3,
|
||||||
|
3: 1,
|
||||||
|
4: 2,
|
||||||
|
6: 4
|
||||||
|
};
|
||||||
|
|
||||||
|
Parser.prototype._reverseFiltered = function(data, width, height) {
|
||||||
|
|
||||||
|
if (this._colorType == 3) { // paletted
|
||||||
|
|
||||||
|
// use values from palette
|
||||||
|
var pxLineLength = width << 2;
|
||||||
|
|
||||||
|
for (var y = 0; y < height; y++) {
|
||||||
|
var pxRowPos = y * pxLineLength;
|
||||||
|
|
||||||
|
for (var x = 0; x < width; x++) {
|
||||||
|
var pxPos = pxRowPos + (x << 2),
|
||||||
|
color = this._palette[data[pxPos]];
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++)
|
||||||
|
data[pxPos + i] = color[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('parsed', data);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import parse from "./parser-sync.js";
|
|
||||||
import pack from "./packer-sync.js";
|
|
||||||
|
|
||||||
export var read = function (buffer, options) {
|
|
||||||
return parse(buffer, options || {});
|
|
||||||
};
|
|
||||||
|
|
||||||
export var write = function (png, options) {
|
|
||||||
return pack(png, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
read,
|
|
||||||
write
|
|
||||||
}
|
|
||||||
288
lib/png.js
Normal file → Executable file
|
|
@ -1,193 +1,149 @@
|
||||||
import Parser from "./parser-async.js";
|
// Copyright (c) 2012 Kuba Niegowski
|
||||||
import Packer from "./packer-async.js";
|
//
|
||||||
import PNGSync from "./png-sync.js";
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
let util = require("util");
|
'use strict';
|
||||||
let Stream = require("stream");
|
|
||||||
|
|
||||||
let PNG = (exports.PNG = function (options) {
|
|
||||||
Stream.call(this);
|
|
||||||
|
|
||||||
options = options || {}; // eslint-disable-line no-param-reassign
|
var util = require('util'),
|
||||||
|
Stream = require('stream'),
|
||||||
|
Parser = require('./parser'),
|
||||||
|
Packer = require('./packer');
|
||||||
|
|
||||||
// coerce pixel dimensions to integers (also coerces undefined -> 0):
|
|
||||||
this.width = options.width | 0;
|
|
||||||
this.height = options.height | 0;
|
|
||||||
|
|
||||||
this.data =
|
var PNG = exports.PNG = function(options) {
|
||||||
this.width > 0 && this.height > 0
|
Stream.call(this);
|
||||||
? Buffer.alloc(4 * this.width * this.height)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (options.fill && this.data) {
|
options = options || {};
|
||||||
this.data.fill(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.gamma = 0;
|
this.width = options.width || 0;
|
||||||
this.readable = this.writable = true;
|
this.height = options.height || 0;
|
||||||
|
|
||||||
this._parser = new Parser(options);
|
this.data = this.width > 0 && this.height > 0
|
||||||
|
? new Buffer(4 * this.width * this.height) : null;
|
||||||
|
|
||||||
this._parser.on("error", this.emit.bind(this, "error"));
|
if(options.fill && this.data){this.data.fill(0)};
|
||||||
this._parser.on("close", this._handleClose.bind(this));
|
|
||||||
this._parser.on("metadata", this._metadata.bind(this));
|
|
||||||
this._parser.on("gamma", this._gamma.bind(this));
|
|
||||||
this._parser.on(
|
|
||||||
"parsed",
|
|
||||||
function (data) {
|
|
||||||
this.data = data;
|
|
||||||
this.emit("parsed", data);
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
this._packer = new Packer(options);
|
this.gamma = 0;
|
||||||
this._packer.on("data", this.emit.bind(this, "data"));
|
this.readable = this.writable = true;
|
||||||
this._packer.on("end", this.emit.bind(this, "end"));
|
|
||||||
this._parser.on("close", this._handleClose.bind(this));
|
this._parser = new Parser(options || {});
|
||||||
this._packer.on("error", this.emit.bind(this, "error"));
|
|
||||||
});
|
this._parser.on('error', this.emit.bind(this, 'error'));
|
||||||
|
this._parser.on('close', this._handleClose.bind(this));
|
||||||
|
this._parser.on('metadata', this._metadata.bind(this));
|
||||||
|
this._parser.on('gamma', this._gamma.bind(this));
|
||||||
|
this._parser.on('parsed', function(data) {
|
||||||
|
this.data = data;
|
||||||
|
this.emit('parsed', data);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this._packer = new Packer(options);
|
||||||
|
this._packer.on('data', this.emit.bind(this, 'data'));
|
||||||
|
this._packer.on('end', this.emit.bind(this, 'end'));
|
||||||
|
this._parser.on('close', this._handleClose.bind(this));
|
||||||
|
this._packer.on('error', this.emit.bind(this, 'error'));
|
||||||
|
|
||||||
|
};
|
||||||
util.inherits(PNG, Stream);
|
util.inherits(PNG, Stream);
|
||||||
|
|
||||||
PNG.sync = PNGSync;
|
|
||||||
|
|
||||||
PNG.prototype.pack = function () {
|
PNG.prototype.pack = function() {
|
||||||
if (!this.data || !this.data.length) {
|
|
||||||
this.emit("error", "No data provided");
|
process.nextTick(function() {
|
||||||
|
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;
|
|
||||||
|
|
||||||
onParsed = function (parsedData) {
|
PNG.prototype.parse = function(data, callback) {
|
||||||
this.removeListener("error", onError);
|
|
||||||
|
|
||||||
this.data = parsedData;
|
if (callback) {
|
||||||
callback(null, this);
|
var onParsed = null, onError = null;
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
onError = function (err) {
|
this.once('parsed', onParsed = function(data) {
|
||||||
this.removeListener("parsed", onParsed);
|
this.removeListener('error', onError);
|
||||||
|
|
||||||
callback(err, null);
|
this.data = data;
|
||||||
}.bind(this);
|
callback(null, this);
|
||||||
|
|
||||||
this.once("parsed", onParsed);
|
}.bind(this));
|
||||||
this.once("error", onError);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.end(data);
|
this.once('error', onError = function(err) {
|
||||||
return this;
|
this.removeListener('parsed', onParsed);
|
||||||
};
|
|
||||||
|
|
||||||
PNG.prototype.write = function (data) {
|
callback(err, null);
|
||||||
this._parser.write(data);
|
}.bind(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.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.adjustGamma = function () {
|
PNG.prototype.write = function(data) {
|
||||||
PNG.adjustGamma(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.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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
let assert = require("assert").ok;
|
|
||||||
let zlib = require("zlib");
|
|
||||||
let util = require("util");
|
|
||||||
|
|
||||||
let kMaxLength = require("buffer").kMaxLength;
|
|
||||||
|
|
||||||
export function Inflate(opts) {
|
|
||||||
if (!(this instanceof Inflate)) {
|
|
||||||
return new Inflate(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts && opts.chunkSize < zlib.Z_MIN_CHUNK) {
|
|
||||||
opts.chunkSize = zlib.Z_MIN_CHUNK;
|
|
||||||
}
|
|
||||||
|
|
||||||
zlib.Inflate.call(this, opts);
|
|
||||||
|
|
||||||
// Node 8 --> 9 compatibility check
|
|
||||||
this._offset = this._offset === undefined ? this._outOffset : this._offset;
|
|
||||||
this._buffer = this._buffer || this._outBuffer;
|
|
||||||
|
|
||||||
if (opts && opts.maxLength != null) {
|
|
||||||
this._maxLength = opts.maxLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createInflate(opts) {
|
|
||||||
return new Inflate(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _close(engine, callback) {
|
|
||||||
if (callback) {
|
|
||||||
process.nextTick(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Caller may invoke .close after a zlib error (which will null _handle).
|
|
||||||
if (!engine._handle) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
engine._handle.close();
|
|
||||||
engine._handle = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Inflate.prototype._processChunk = function (chunk, flushFlag, asyncCb) {
|
|
||||||
if (typeof asyncCb === "function") {
|
|
||||||
return zlib.Inflate._processChunk.call(this, chunk, flushFlag, asyncCb);
|
|
||||||
}
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
let availInBefore = chunk && chunk.length;
|
|
||||||
let availOutBefore = this._chunkSize - this._offset;
|
|
||||||
let leftToInflate = this._maxLength;
|
|
||||||
let inOff = 0;
|
|
||||||
|
|
||||||
let buffers = [];
|
|
||||||
let nread = 0;
|
|
||||||
|
|
||||||
let error;
|
|
||||||
this.on("error", function (err) {
|
|
||||||
error = err;
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleChunk(availInAfter, availOutAfter) {
|
|
||||||
if (self._hadError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let have = availOutBefore - availOutAfter;
|
|
||||||
assert(have >= 0, "have should not go down");
|
|
||||||
|
|
||||||
if (have > 0) {
|
|
||||||
let out = self._buffer.slice(self._offset, self._offset + have);
|
|
||||||
self._offset += have;
|
|
||||||
|
|
||||||
if (out.length > leftToInflate) {
|
|
||||||
out = out.slice(0, leftToInflate);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffers.push(out);
|
|
||||||
nread += out.length;
|
|
||||||
leftToInflate -= out.length;
|
|
||||||
|
|
||||||
if (leftToInflate === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availOutAfter === 0 || self._offset >= self._chunkSize) {
|
|
||||||
availOutBefore = self._chunkSize;
|
|
||||||
self._offset = 0;
|
|
||||||
self._buffer = Buffer.allocUnsafe(self._chunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (availOutAfter === 0) {
|
|
||||||
inOff += availInBefore - availInAfter;
|
|
||||||
availInBefore = availInAfter;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(this._handle, "zlib binding closed");
|
|
||||||
let res;
|
|
||||||
do {
|
|
||||||
res = this._handle.writeSync(
|
|
||||||
flushFlag,
|
|
||||||
chunk, // in
|
|
||||||
inOff, // in_off
|
|
||||||
availInBefore, // in_len
|
|
||||||
this._buffer, // out
|
|
||||||
this._offset, //out_off
|
|
||||||
availOutBefore
|
|
||||||
); // out_len
|
|
||||||
// Node 8 --> 9 compatibility check
|
|
||||||
res = res || this._writeState;
|
|
||||||
} while (!this._hadError && handleChunk(res[0], res[1]));
|
|
||||||
|
|
||||||
if (this._hadError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nread >= kMaxLength) {
|
|
||||||
_close(this);
|
|
||||||
throw new RangeError(
|
|
||||||
"Cannot create final Buffer. It would be larger than 0x" +
|
|
||||||
kMaxLength.toString(16) +
|
|
||||||
" bytes"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let buf = Buffer.concat(buffers, nread);
|
|
||||||
_close(this);
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
util.inherits(Inflate, zlib.Inflate);
|
|
||||||
|
|
||||||
function zlibBufferSync(engine, buffer) {
|
|
||||||
if (typeof buffer === "string") {
|
|
||||||
buffer = Buffer.from(buffer);
|
|
||||||
}
|
|
||||||
if (!(buffer instanceof Buffer)) {
|
|
||||||
throw new TypeError("Not a string or buffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
let flushFlag = engine._finishFlushFlag;
|
|
||||||
if (flushFlag == null) {
|
|
||||||
flushFlag = zlib.Z_FINISH;
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine._processChunk(buffer, flushFlag);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inflateSync(buffer, opts) {
|
|
||||||
return zlibBufferSync(new Inflate(opts), buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default inflateSync;
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
let SyncReader = function (buffer) {
|
|
||||||
this._buffer = buffer;
|
|
||||||
this._reads = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SyncReader;
|
|
||||||
|
|
||||||
SyncReader.prototype.read = function (length, callback) {
|
|
||||||
this._reads.push({
|
|
||||||
length: Math.abs(length), // if length < 0 then at most this length
|
|
||||||
allowLess: length < 0,
|
|
||||||
func: callback,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SyncReader.prototype.process = function () {
|
|
||||||
// as long as there is any data and read requests
|
|
||||||
while (this._reads.length > 0 && this._buffer.length) {
|
|
||||||
let read = this._reads[0];
|
|
||||||
|
|
||||||
if (
|
|
||||||
this._buffer.length &&
|
|
||||||
(this._buffer.length >= read.length || read.allowLess)
|
|
||||||
) {
|
|
||||||
// ok there is any data so that we can satisfy this request
|
|
||||||
this._reads.shift(); // == read
|
|
||||||
|
|
||||||
let buf = this._buffer;
|
|
||||||
|
|
||||||
this._buffer = buf.slice(read.length);
|
|
||||||
|
|
||||||
read.func.call(this, buf.slice(0, read.length));
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._reads.length > 0) {
|
|
||||||
return new Error("There are some read requests waitng on finished stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._buffer.length > 0) {
|
|
||||||
return new Error("unrecognised content at end of stream");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
68
package.json
|
|
@ -1,73 +1,29 @@
|
||||||
{
|
{
|
||||||
"name": "pngjs",
|
"name": "node-png",
|
||||||
"version": "5.0.0",
|
"version": "0.4.3",
|
||||||
"description": "PNG encoder/decoder in pure JS, supporting any bit size & interlace, async & sync with full test suite.",
|
"description": "Simple PNG encoder/decoder",
|
||||||
"contributors": [
|
"author": "Kuba Niegowski",
|
||||||
"Alexandre Paré",
|
"contributors": [],
|
||||||
"Gaurav Mali",
|
"homepage": "https://github.com/niegowski/node-pngjs/",
|
||||||
"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": ">=10.13.0"
|
"node": "0.8.x"
|
||||||
},
|
},
|
||||||
"main": "./lib/png.js",
|
"main": "./lib/png.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
"lib": "lib",
|
"example": "examples"
|
||||||
"example": "examples",
|
|
||||||
"test": "test"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn prepublish",
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
"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/lukeapage/pngjs.git"
|
"url": "git://github.com/brighthas/node-pngjs.git"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/lukeapage/pngjs/issues"
|
"url": "https://github.com/brighthas/node-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
|
|
@ -1 +0,0 @@
|
||||||
bg.png
|
|
||||||
BIN
test/bg-ref.png
|
Before Width: | Height: | Size: 119 B |
|
|
@ -1,46 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
let fs = require("fs");
|
|
||||||
let PNG = require("../lib/png").PNG;
|
|
||||||
let test = require("tape");
|
|
||||||
let bufferEqual = require("buffer-equal");
|
|
||||||
|
|
||||||
test("outputs background, created from scratch", function (t) {
|
|
||||||
t.timeoutAfter(1000 * 60 * 5);
|
|
||||||
|
|
||||||
let png = new PNG({
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
filterType: -1,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let y = 0; y < png.height; y++) {
|
|
||||||
for (let x = 0; x < png.width; x++) {
|
|
||||||
let idx = (png.width * y + x) << 2;
|
|
||||||
|
|
||||||
let col = (x < png.width >> 1) ^ (y < png.height >> 1) ? 0xe5 : 0xff;
|
|
||||||
|
|
||||||
png.data[idx] = col;
|
|
||||||
png.data[idx + 1] = col;
|
|
||||||
png.data[idx + 2] = col;
|
|
||||||
png.data[idx + 3] = 0xff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
png
|
|
||||||
.pack()
|
|
||||||
.pipe(fs.createWriteStream(__dirname + "/bg.png"))
|
|
||||||
.on("finish", function () {
|
|
||||||
let out = fs.readFileSync(__dirname + "/bg.png");
|
|
||||||
let ref = fs.readFileSync(__dirname + "/bg-ref.png");
|
|
||||||
|
|
||||||
let isBufferEqual = bufferEqual(out, ref);
|
|
||||||
t.ok(isBufferEqual, "compares with working file ok");
|
|
||||||
|
|
||||||
if (!isBufferEqual) {
|
|
||||||
console.log(out.length, ref.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
t.end();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
BIN
test/bg.png
|
Before Width: | Height: | Size: 119 B |
|
|
@ -1,105 +0,0 @@
|
||||||
let fs = require("fs");
|
|
||||||
let PNG = require("../lib/png").PNG;
|
|
||||||
let test = require("tape");
|
|
||||||
|
|
||||||
let noLargeOption = process.argv.indexOf("nolarge") >= 0;
|
|
||||||
|
|
||||||
fs.readdir(__dirname + "/in/", function (err, files) {
|
|
||||||
if (err) throw err;
|
|
||||||
|
|
||||||
files = files.filter(function (file) {
|
|
||||||
return (
|
|
||||||
(!noLargeOption || !file.match(/large/i)) &&
|
|
||||||
Boolean(file.match(/\.png$/i))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Converting images");
|
|
||||||
|
|
||||||
files.forEach(function (file) {
|
|
||||||
let expectedError = false;
|
|
||||||
if (file.match(/^x/)) {
|
|
||||||
expectedError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
test("convert sync - " + file, function (t) {
|
|
||||||
t.timeoutAfter(1000 * 60 * 5);
|
|
||||||
|
|
||||||
let data = fs.readFileSync(__dirname + "/in/" + file);
|
|
||||||
let png;
|
|
||||||
try {
|
|
||||||
png = PNG.sync.read(data);
|
|
||||||
} catch (e) {
|
|
||||||
if (!expectedError) {
|
|
||||||
t.fail(
|
|
||||||
"Unexpected error parsing.." +
|
|
||||||
file +
|
|
||||||
"\n" +
|
|
||||||
e.message +
|
|
||||||
"\n" +
|
|
||||||
e.stack
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
t.pass("completed");
|
|
||||||
}
|
|
||||||
return t.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectedError) {
|
|
||||||
t.fail("Sync: Error expected, parsed fine .. - " + file);
|
|
||||||
return t.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
let outpng = new PNG();
|
|
||||||
outpng.gamma = png.gamma;
|
|
||||||
outpng.data = png.data;
|
|
||||||
outpng.width = png.width;
|
|
||||||
outpng.height = png.height;
|
|
||||||
outpng.pack().pipe(
|
|
||||||
fs
|
|
||||||
.createWriteStream(__dirname + "/outsync/" + file)
|
|
||||||
.on("finish", function () {
|
|
||||||
t.pass("completed");
|
|
||||||
t.end();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("convert async - " + file, function (t) {
|
|
||||||
t.timeoutAfter(1000 * 60 * 5);
|
|
||||||
|
|
||||||
fs.createReadStream(__dirname + "/in/" + file)
|
|
||||||
.pipe(new PNG())
|
|
||||||
.on("error", function (err) {
|
|
||||||
if (!expectedError) {
|
|
||||||
t.fail(
|
|
||||||
"Async: Unexpected error parsing.." +
|
|
||||||
file +
|
|
||||||
"\n" +
|
|
||||||
err.message +
|
|
||||||
"\n" +
|
|
||||||
err.stack
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
t.pass("completed");
|
|
||||||
}
|
|
||||||
t.end();
|
|
||||||
})
|
|
||||||
.on("parsed", function () {
|
|
||||||
if (expectedError) {
|
|
||||||
t.fail("Async: Error expected, parsed fine .." + file);
|
|
||||||
return t.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pack().pipe(
|
|
||||||
fs
|
|
||||||
.createWriteStream(__dirname + "/out/" + file)
|
|
||||||
.on("finish", function () {
|
|
||||||
t.pass("completed");
|
|
||||||
t.end();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
let serveStatic = require("serve-static");
|
|
||||||
let http = require("http");
|
|
||||||
let connect = require("connect");
|
|
||||||
|
|
||||||
let app = connect();
|
|
||||||
let server = http.createServer(app);
|
|
||||||
|
|
||||||
app.use(serveStatic("test"));
|
|
||||||
|
|
||||||
server.listen(8000);
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
server.close();
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("Tests available at http://localhost:8000/");
|
|
||||||
|
|
@ -1,409 +0,0 @@
|
||||||
// js-imagediff 1.0.3
|
|
||||||
// (c) 2011-2012 Carl Sutherland, Humble Software
|
|
||||||
// Distributed under the MIT License
|
|
||||||
// For original source and documentation visit:
|
|
||||||
// http://www.github.com/HumbleSoftware/js-imagediff
|
|
||||||
|
|
||||||
(function (name, definition) {
|
|
||||||
var root = this;
|
|
||||||
if (typeof module !== 'undefined') {
|
|
||||||
try {
|
|
||||||
var Canvas = require('canvas');
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(
|
|
||||||
e.message + '\n' +
|
|
||||||
'Please see https://github.com/HumbleSoftware/js-imagediff#cannot-find-module-canvas\n'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
module.exports = definition(root, name, Canvas);
|
|
||||||
} else if (typeof define === 'function' && typeof define.amd === 'object') {
|
|
||||||
define(definition);
|
|
||||||
} else {
|
|
||||||
root[name] = definition(root, name);
|
|
||||||
}
|
|
||||||
})('imagediff', function (root, name, Canvas) {
|
|
||||||
|
|
||||||
var
|
|
||||||
TYPE_ARRAY = /\[object Array\]/i,
|
|
||||||
TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i,
|
|
||||||
TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i,
|
|
||||||
TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i,
|
|
||||||
TYPE_IMAGE_DATA = /\[object ImageData\]/i,
|
|
||||||
|
|
||||||
UNDEFINED = 'undefined',
|
|
||||||
|
|
||||||
canvas = getCanvas(),
|
|
||||||
context = canvas.getContext('2d'),
|
|
||||||
previous = root[name],
|
|
||||||
imagediff, jasmine;
|
|
||||||
|
|
||||||
// Creation
|
|
||||||
function getCanvas (width, height) {
|
|
||||||
var
|
|
||||||
canvas = Canvas ?
|
|
||||||
new Canvas() :
|
|
||||||
document.createElement('canvas');
|
|
||||||
if (width) canvas.width = width;
|
|
||||||
if (height) canvas.height = height;
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
function getImageData (width, height) {
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
context.clearRect(0, 0, width, height);
|
|
||||||
return context.createImageData(width, height);
|
|
||||||
}
|
|
||||||
// expost canvas module
|
|
||||||
function getCanvasRef() {
|
|
||||||
return Canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Type Checking
|
|
||||||
function isImage (object) {
|
|
||||||
return isType(object, TYPE_IMAGE);
|
|
||||||
}
|
|
||||||
function isCanvas (object) {
|
|
||||||
return isType(object, TYPE_CANVAS);
|
|
||||||
}
|
|
||||||
function isContext (object) {
|
|
||||||
return isType(object, TYPE_CONTEXT);
|
|
||||||
}
|
|
||||||
function isImageData (object) {
|
|
||||||
return !!(object &&
|
|
||||||
isType(object, TYPE_IMAGE_DATA) &&
|
|
||||||
typeof(object.width) !== UNDEFINED &&
|
|
||||||
typeof(object.height) !== UNDEFINED &&
|
|
||||||
typeof(object.data) !== UNDEFINED);
|
|
||||||
}
|
|
||||||
function isImageType (object) {
|
|
||||||
return (
|
|
||||||
isImage(object) ||
|
|
||||||
isCanvas(object) ||
|
|
||||||
isContext(object) ||
|
|
||||||
isImageData(object)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function isType (object, type) {
|
|
||||||
return typeof (object) === 'object' && !!Object.prototype.toString.apply(object).match(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Type Conversion
|
|
||||||
function copyImageData (imageData) {
|
|
||||||
var
|
|
||||||
height = imageData.height,
|
|
||||||
width = imageData.width,
|
|
||||||
data = imageData.data,
|
|
||||||
newImageData, newData, i;
|
|
||||||
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
newImageData = context.getImageData(0, 0, width, height);
|
|
||||||
newData = newImageData.data;
|
|
||||||
|
|
||||||
for (i = imageData.data.length; i--;) {
|
|
||||||
newData[i] = data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return newImageData;
|
|
||||||
}
|
|
||||||
function toImageData (object) {
|
|
||||||
if (isImage(object)) { return toImageDataFromImage(object); }
|
|
||||||
if (isCanvas(object)) { return toImageDataFromCanvas(object); }
|
|
||||||
if (isContext(object)) { return toImageDataFromContext(object); }
|
|
||||||
if (isImageData(object)) { return object; }
|
|
||||||
}
|
|
||||||
function toImageDataFromImage (image) {
|
|
||||||
var
|
|
||||||
height = image.height,
|
|
||||||
width = image.width;
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
context.clearRect(0, 0, width, height);
|
|
||||||
context.drawImage(image, 0, 0);
|
|
||||||
return context.getImageData(0, 0, width, height);
|
|
||||||
}
|
|
||||||
function toImageDataFromCanvas (canvas) {
|
|
||||||
var
|
|
||||||
height = canvas.height,
|
|
||||||
width = canvas.width,
|
|
||||||
context = canvas.getContext('2d');
|
|
||||||
return context.getImageData(0, 0, width, height);
|
|
||||||
}
|
|
||||||
function toImageDataFromContext (context) {
|
|
||||||
var
|
|
||||||
canvas = context.canvas,
|
|
||||||
height = canvas.height,
|
|
||||||
width = canvas.width;
|
|
||||||
return context.getImageData(0, 0, width, height);
|
|
||||||
}
|
|
||||||
function toCanvas (object) {
|
|
||||||
var
|
|
||||||
data = toImageData(object),
|
|
||||||
canvas = getCanvas(data.width, data.height),
|
|
||||||
context = canvas.getContext('2d');
|
|
||||||
|
|
||||||
context.putImageData(data, 0, 0);
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ImageData Equality Operators
|
|
||||||
function equalWidth (a, b) {
|
|
||||||
return a.width === b.width;
|
|
||||||
}
|
|
||||||
function equalHeight (a, b) {
|
|
||||||
return a.height === b.height;
|
|
||||||
}
|
|
||||||
function equalDimensions (a, b) {
|
|
||||||
return equalHeight(a, b) && equalWidth(a, b);
|
|
||||||
}
|
|
||||||
function equal (a, b, tolerance) {
|
|
||||||
|
|
||||||
var
|
|
||||||
aData = a.data,
|
|
||||||
bData = b.data,
|
|
||||||
length = aData.length,
|
|
||||||
i;
|
|
||||||
|
|
||||||
tolerance = tolerance || 0;
|
|
||||||
|
|
||||||
if (!equalDimensions(a, b)) return false;
|
|
||||||
for (i = length; i--;) {
|
|
||||||
if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) {
|
|
||||||
var x = i % (a.width * 4);
|
|
||||||
var y = (i - x) / (a.width * 4);
|
|
||||||
var color = x % 4;
|
|
||||||
x = (x - color) / 4;
|
|
||||||
console.log("Difference x", x, "y", y, ["R", "G", "B", "A"][color], " - ", aData[i], " !== ", bData[i]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Diff
|
|
||||||
function diff (a, b, options) {
|
|
||||||
return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b, options);
|
|
||||||
}
|
|
||||||
function diffEqual (a, b, options) {
|
|
||||||
|
|
||||||
var
|
|
||||||
height = a.height,
|
|
||||||
width = a.width,
|
|
||||||
c = getImageData(width, height), // c = a - b
|
|
||||||
aData = a.data,
|
|
||||||
bData = b.data,
|
|
||||||
cData = c.data,
|
|
||||||
length = cData.length,
|
|
||||||
row, column,
|
|
||||||
i, j, k, v;
|
|
||||||
|
|
||||||
for (i = 0; i < length; i += 4) {
|
|
||||||
cData[i] = Math.abs(aData[i] - bData[i]);
|
|
||||||
cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
|
|
||||||
cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
|
|
||||||
cData[i+3] = Math.abs(255 - Math.abs(aData[i+3] - bData[i+3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
function diffUnequal (a, b, options) {
|
|
||||||
|
|
||||||
var
|
|
||||||
height = Math.max(a.height, b.height),
|
|
||||||
width = Math.max(a.width, b.width),
|
|
||||||
c = getImageData(width, height), // c = a - b
|
|
||||||
aData = a.data,
|
|
||||||
bData = b.data,
|
|
||||||
cData = c.data,
|
|
||||||
align = options && options.align,
|
|
||||||
rowOffset,
|
|
||||||
columnOffset,
|
|
||||||
row, column,
|
|
||||||
i, j, k, v;
|
|
||||||
|
|
||||||
|
|
||||||
for (i = cData.length - 1; i > 0; i = i - 4) {
|
|
||||||
cData[i] = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add First Image
|
|
||||||
offsets(a);
|
|
||||||
for (row = a.height; row--;){
|
|
||||||
for (column = a.width; column--;) {
|
|
||||||
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
|
|
||||||
j = 4 * (row * a.width + column);
|
|
||||||
cData[i+0] = aData[j+0]; // r
|
|
||||||
cData[i+1] = aData[j+1]; // g
|
|
||||||
cData[i+2] = aData[j+2]; // b
|
|
||||||
// cData[i+3] = aData[j+3]; // a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtract Second Image
|
|
||||||
offsets(b);
|
|
||||||
for (row = b.height; row--;){
|
|
||||||
for (column = b.width; column--;) {
|
|
||||||
i = 4 * ((row + rowOffset) * width + (column + columnOffset));
|
|
||||||
j = 4 * (row * b.width + column);
|
|
||||||
cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
|
|
||||||
cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
|
|
||||||
cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
function offsets (imageData) {
|
|
||||||
if (align === 'top') {
|
|
||||||
rowOffset = 0;
|
|
||||||
columnOffset = 0;
|
|
||||||
} else {
|
|
||||||
rowOffset = Math.floor((height - imageData.height) / 2);
|
|
||||||
columnOffset = Math.floor((width - imageData.width) / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
function checkType () {
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < arguments.length; i++) {
|
|
||||||
if (!isImageType(arguments[i])) {
|
|
||||||
throw {
|
|
||||||
name : 'ImageTypeError',
|
|
||||||
message : 'Submitted object was not an image.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Jasmine Matchers
|
|
||||||
function get (element, content) {
|
|
||||||
element = document.createElement(element);
|
|
||||||
if (element && content) {
|
|
||||||
element.innerHTML = content;
|
|
||||||
}
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
jasmine = {
|
|
||||||
|
|
||||||
toBeImageData : function () {
|
|
||||||
return imagediff.isImageData(this.actual);
|
|
||||||
},
|
|
||||||
|
|
||||||
toImageDiffEqual : function (expected, tolerance) {
|
|
||||||
|
|
||||||
if (typeof (document) !== UNDEFINED) {
|
|
||||||
this.message = function () {
|
|
||||||
var
|
|
||||||
div = get('div'),
|
|
||||||
a = get('div', '<div>Actual:</div>'),
|
|
||||||
b = get('div', '<div>Expected:</div>'),
|
|
||||||
c = get('div', '<div>Diff:</div>'),
|
|
||||||
diff = imagediff.diff(this.actual, expected),
|
|
||||||
canvas = getCanvas(),
|
|
||||||
context;
|
|
||||||
|
|
||||||
canvas.height = diff.height;
|
|
||||||
canvas.width = diff.width;
|
|
||||||
|
|
||||||
div.style.overflow = 'hidden';
|
|
||||||
a.style.float = 'left';
|
|
||||||
b.style.float = 'left';
|
|
||||||
c.style.float = 'left';
|
|
||||||
|
|
||||||
context = canvas.getContext('2d');
|
|
||||||
context.putImageData(diff, 0, 0);
|
|
||||||
|
|
||||||
a.appendChild(toCanvas(this.actual));
|
|
||||||
b.appendChild(toCanvas(expected));
|
|
||||||
c.appendChild(canvas);
|
|
||||||
|
|
||||||
div.appendChild(a);
|
|
||||||
div.appendChild(b);
|
|
||||||
div.appendChild(c);
|
|
||||||
|
|
||||||
return [
|
|
||||||
div,
|
|
||||||
"Expected not to be equal."
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return imagediff.equal(this.actual, expected, tolerance);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Image Output
|
|
||||||
function imageDataToPNG (imageData, outputFile, callback) {
|
|
||||||
|
|
||||||
var
|
|
||||||
canvas = toCanvas(imageData),
|
|
||||||
base64Data,
|
|
||||||
decodedImage;
|
|
||||||
|
|
||||||
callback = callback || Function;
|
|
||||||
|
|
||||||
base64Data = canvas.toDataURL().replace(/^data:image\/\w+;base64,/,"");
|
|
||||||
decodedImage = Buffer.from(base64Data, 'base64');
|
|
||||||
require('fs').writeFile(outputFile, decodedImage, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Definition
|
|
||||||
imagediff = {
|
|
||||||
|
|
||||||
createCanvas : getCanvas,
|
|
||||||
createImageData : getImageData,
|
|
||||||
getCanvasRef: getCanvasRef,
|
|
||||||
|
|
||||||
isImage : isImage,
|
|
||||||
isCanvas : isCanvas,
|
|
||||||
isContext : isContext,
|
|
||||||
isImageData : isImageData,
|
|
||||||
isImageType : isImageType,
|
|
||||||
|
|
||||||
toImageData : function (object) {
|
|
||||||
checkType(object);
|
|
||||||
if (isImageData(object)) { return copyImageData(object); }
|
|
||||||
return toImageData(object);
|
|
||||||
},
|
|
||||||
|
|
||||||
equal : function (a, b, tolerance) {
|
|
||||||
checkType(a, b);
|
|
||||||
a = toImageData(a);
|
|
||||||
b = toImageData(b);
|
|
||||||
return equal(a, b, tolerance);
|
|
||||||
},
|
|
||||||
diff : function (a, b, options) {
|
|
||||||
checkType(a, b);
|
|
||||||
a = toImageData(a);
|
|
||||||
b = toImageData(b);
|
|
||||||
return diff(a, b, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
jasmine : jasmine,
|
|
||||||
|
|
||||||
// Compatibility
|
|
||||||
noConflict : function () {
|
|
||||||
root[name] = previous;
|
|
||||||
return imagediff;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof module !== 'undefined') {
|
|
||||||
imagediff.imageDataToPNG = imageDataToPNG;
|
|
||||||
}
|
|
||||||
|
|
||||||
return imagediff;
|
|
||||||
});
|
|
||||||
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 217 B |
|
Before Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 254 B |
|
Before Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 132 B |
|
Before Width: | Height: | Size: 193 B |
|
Before Width: | Height: | Size: 327 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 104 B |
|
Before Width: | Height: | Size: 145 B |
|
Before Width: | Height: | Size: 167 B |
|
Before Width: | Height: | Size: 302 B |
|
Before Width: | Height: | Size: 112 B |
|
Before Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 214 B |