chore: add jimp dependency, icon padding script, and update configuration
This commit is contained in:
+157
@@ -0,0 +1,157 @@
|
||||
# v0.22.8 (Thu May 11 2023)
|
||||
|
||||
#### 🚀 Enhancement
|
||||
|
||||
- Fix: convolute not defaulting to 0, 0 as starting point [#1228](https://github.com/jimp-dev/jimp/pull/1228) ([@sjoerd108](https://github.com/sjoerd108))
|
||||
|
||||
#### Authors: 1
|
||||
|
||||
- Sjoerd ([@sjoerd108](https://github.com/sjoerd108))
|
||||
|
||||
---
|
||||
|
||||
# v0.22.6 (Fri Feb 24 2023)
|
||||
|
||||
:tada: This release contains work from a new contributor! :tada:
|
||||
|
||||
Thank you, Daniell ([@daniellwdb](https://github.com/daniellwdb)), for all your work!
|
||||
|
||||
#### 🐛 Bug Fix
|
||||
|
||||
- Export ColorActionName enum [#1205](https://github.com/jimp-dev/jimp/pull/1205) ([@daniellwdb](https://github.com/daniellwdb))
|
||||
|
||||
#### Authors: 1
|
||||
|
||||
- Daniell ([@daniellwdb](https://github.com/daniellwdb))
|
||||
|
||||
---
|
||||
|
||||
# v0.22.4 (Tue Feb 07 2023)
|
||||
|
||||
#### 🚀 Enhancement
|
||||
|
||||
- update tinycolor2 [#1187](https://github.com/jimp-dev/jimp/pull/1187) ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
#### Authors: 1
|
||||
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
---
|
||||
|
||||
# v0.22.1 (Mon Feb 06 2023)
|
||||
|
||||
#### 🏠 Internal
|
||||
|
||||
- rename master to main [#1169](https://github.com/jimp-dev/jimp/pull/1169) ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
#### Authors: 1
|
||||
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
---
|
||||
|
||||
# v0.22.0 (Mon Feb 06 2023)
|
||||
|
||||
#### 🏠 Internal
|
||||
|
||||
- switch from should to expect [#1163](https://github.com/jimp-dev/jimp/pull/1163) ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
- Change test matching strategy to include all test files [#1161](https://github.com/jimp-dev/jimp/pull/1161) ([@danielholmes](https://github.com/danielholmes))
|
||||
|
||||
#### Authors: 2
|
||||
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
- Daniel Holmes ([@danielholmes](https://github.com/danielholmes))
|
||||
|
||||
---
|
||||
|
||||
# v0.21.0 (Sun Feb 05 2023)
|
||||
|
||||
### Release Notes
|
||||
|
||||
#### Babel Refactor ([#1149](https://github.com/jimp-dev/jimp/pull/1149))
|
||||
|
||||
Marking this as a "breaking release" because it might change what deps need to be installed.
|
||||
|
||||
All modules should be exported as valid cjs and esm
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
#### 💥 Breaking Change
|
||||
|
||||
- Babel Refactor [#1149](https://github.com/jimp-dev/jimp/pull/1149) ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
#### Authors: 1
|
||||
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
---
|
||||
|
||||
# v0.17.6 (Sat Feb 04 2023)
|
||||
|
||||
#### 🐛 Bug Fix
|
||||
|
||||
- Fix types not assignable to 'ColorActionName'. [#1086](https://github.com/jimp-dev/jimp/pull/1086) ([@lucyyyyyyy](https://github.com/lucyyyyyyy) [@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
#### Authors: 2
|
||||
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
- Lucy ([@lucyyyyyyy](https://github.com/lucyyyyyyy))
|
||||
|
||||
---
|
||||
|
||||
# v0.17.3 (Sat Feb 04 2023)
|
||||
|
||||
#### 🐛 Bug Fix
|
||||
|
||||
- Fix EDGE_WRAP overwriting X with Y when Y < 0 [#1135](https://github.com/jimp-dev/jimp/pull/1135) ([@sjoerd108](https://github.com/sjoerd108) [@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
#### Authors: 2
|
||||
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
- Sjoerd ([@sjoerd108](https://github.com/sjoerd108))
|
||||
|
||||
---
|
||||
|
||||
# v0.16.3 (Sat Feb 04 2023)
|
||||
|
||||
#### 🐛 Bug Fix
|
||||
|
||||
- Fix edgeHandling types [#1080](https://github.com/jimp-dev/jimp/pull/1080) ([@domdomegg](https://github.com/domdomegg))
|
||||
|
||||
#### ⚠️ Pushed to `main`
|
||||
|
||||
- update linting ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
- upgrade prettier ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
#### Authors: 2
|
||||
|
||||
- Adam Jones ([@domdomegg](https://github.com/domdomegg))
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
---
|
||||
|
||||
# v0.11.0 (Fri May 15 2020)
|
||||
|
||||
#### 🚀 Enhancement
|
||||
|
||||
- Removed Core-JS as a dependency. [#882](https://github.com/oliver-moran/jimp/pull/882) ([@EricRabil](https://github.com/EricRabil))
|
||||
|
||||
#### Authors: 1
|
||||
|
||||
- Eric Rabil ([@EricRabil](https://github.com/EricRabil))
|
||||
|
||||
---
|
||||
|
||||
# v0.9.3 (Tue Nov 26 2019)
|
||||
|
||||
#### 🐛 Bug Fix
|
||||
|
||||
- `@jimp/cli`, `@jimp/core`, `@jimp/custom`, `jimp`, `@jimp/plugin-blit`, `@jimp/plugin-blur`, `@jimp/plugin-circle`, `@jimp/plugin-color`, `@jimp/plugin-contain`, `@jimp/plugin-cover`, `@jimp/plugin-crop`, `@jimp/plugin-displace`, `@jimp/plugin-dither`, `@jimp/plugin-fisheye`, `@jimp/plugin-flip`, `@jimp/plugin-gaussian`, `@jimp/plugin-invert`, `@jimp/plugin-mask`, `@jimp/plugin-normalize`, `@jimp/plugin-print`, `@jimp/plugin-resize`, `@jimp/plugin-rotate`, `@jimp/plugin-scale`, `@jimp/plugin-shadow`, `@jimp/plugin-threshold`, `@jimp/plugins`, `@jimp/test-utils`, `@jimp/bmp`, `@jimp/gif`, `@jimp/jpeg`, `@jimp/png`, `@jimp/tiff`, `@jimp/types`, `@jimp/utils`
|
||||
- Fix regeneratorRuntime errors [#815](https://github.com/oliver-moran/jimp/pull/815) ([@crutchcorn](https://github.com/crutchcorn) [@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
|
||||
#### Authors: 2
|
||||
|
||||
- Andrew Lisowski ([@hipstersmoothie](https://github.com/hipstersmoothie))
|
||||
- Corbin Crutchley ([@crutchcorn](https://github.com/crutchcorn))
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Oliver Moran
|
||||
|
||||
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.
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
<div align="center">
|
||||
<img width="200" height="200"
|
||||
src="https://s3.amazonaws.com/pix.iemoji.com/images/emoji/apple/ios-11/256/crayon.png">
|
||||
<h1>@jimp/plugin-color</h1>
|
||||
<p>Jimp color methods.</p>
|
||||
</div>
|
||||
|
||||
Bitmap manipulation to adjust the color in an image.
|
||||
|
||||
## color
|
||||
|
||||
Apply multiple color modification rules
|
||||
|
||||
- @param {array} actions list of color modification rules, in following format: { apply: '<rule-name>', params: [ <rule-parameters> ] }
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.color([{ apply: "red", params: [100] }]);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
Jimp supports advanced colour manipulation using a single method as follows:
|
||||
|
||||
```js
|
||||
image.color([
|
||||
{ apply: "hue", params: [-90] },
|
||||
{ apply: "lighten", params: [50] },
|
||||
{ apply: "xor", params: ["#06D"] },
|
||||
]);
|
||||
```
|
||||
|
||||
The method supports the following modifiers:
|
||||
|
||||
| Modifier | Description |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **lighten** {amount} | Lighten the color a given amount, from 0 to 100. Providing 100 will always return white (works through [TinyColor](https://github.com/bgrins/TinyColor)) |
|
||||
| **brighten** {amount} | Brighten the color a given amount, from 0 to 100 (works through [TinyColor](https://github.com/bgrins/TinyColor)) |
|
||||
| **darken** {amount} | Darken the color a given amount, from 0 to 100. Providing 100 will always return black (works through [TinyColor](https://github.com/bgrins/TinyColor)) |
|
||||
| **desaturate** {amount} | Desaturate the color a given amount, from 0 to 100. Providing 100 will is the same as calling greyscale (works through [TinyColor](https://github.com/bgrins/TinyColor)) |
|
||||
| **saturate** {amount} | Saturate the color a given amount, from 0 to 100 (works through [TinyColor](https://github.com/bgrins/TinyColor)) |
|
||||
| **greyscale** {amount} | Completely desaturates a color into greyscale (works through [TinyColor](https://github.com/bgrins/TinyColor)) |
|
||||
| **spin** {degree} | Spin the hue a given amount, from -360 to 360. Calling with 0, 360, or -360 will do nothing - since it sets the hue back to what it was before. (works through [TinyColor](https://github.com/bgrins/TinyColor)) |
|
||||
| **hue** {degree} | Alias for **spin** |
|
||||
| **mix** {color, amount} | Mixes colors by their RGB component values. Amount is opacity of overlaying color |
|
||||
| **tint** {amount} | Same as applying **mix** with white color |
|
||||
| **shade** {amount} | Same as applying **mix** with black color |
|
||||
| **xor** {color} | Treats the two colors as bitfields and applies an XOR operation to the red, green, and blue components |
|
||||
| **red** {amount} | Modify Red component by a given amount |
|
||||
| **green** {amount} | Modify Green component by a given amount |
|
||||
| **blue** {amount} | Modify Blue component by a given amount |
|
||||
|
||||
## brightness
|
||||
|
||||
Adjusts the brightness of the image
|
||||
|
||||
- @param {number} val the amount to adjust the brightness, a number between -1 and +1
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.brightness(20);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## contrast
|
||||
|
||||
Adjusts the contrast of the image
|
||||
|
||||
- @param {number} val the amount to adjust the contrast, a number between -1 and +1
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.contrast(70);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## posterize
|
||||
|
||||
Apply a posterize effect
|
||||
|
||||
- @param {number} n the amount to adjust the contrast, minimum threshold is two
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.posterize(5);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## opacity
|
||||
|
||||
Multiplies the opacity of each pixel by a factor between 0 and 1
|
||||
|
||||
- @param {number} f A number, the factor by which to multiply the opacity of each pixel
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.opacity(80);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## sepia
|
||||
|
||||
Applies a sepia tone to the image
|
||||
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.sepia();
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## fade
|
||||
|
||||
Fades each pixel by a factor between 0 and 1
|
||||
|
||||
- @param {number} f A number from 0 to 1. 0 will haven no effect. 1 will turn the image completely transparent.
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.fade(0.7);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## convolution
|
||||
|
||||
Sum neighbor pixels weighted by the kernel matrix. You can find a nice explanation with examples at [GIMP's Convolution Matrix plugin](https://docs.gimp.org/2.6/en/plug-in-convmatrix.html)
|
||||
|
||||
- @param {array} kernel a matrix to weight the neighbors sum
|
||||
- @param {number} edgeHandling (optional) define how to sum pixels from outside the border
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.convolution(
|
||||
[
|
||||
[-1, -1, -1],
|
||||
[-1, 8, -1],
|
||||
[-1, -1, -1],
|
||||
],
|
||||
jimp.EDGE_EXTEND
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## opaque
|
||||
|
||||
Set the alpha channel on every pixel to fully opaque
|
||||
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.opaque();
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## pixelate
|
||||
|
||||
Pixelates the image or a region
|
||||
|
||||
- @param {number} size the size of the pixels
|
||||
- @param {number} x (optional) the x position of the region to pixelate
|
||||
- @param {number} y (optional) the y position of the region to pixelate
|
||||
- @param {number} w (optional) the width of the region to pixelate
|
||||
- @param {number} h (optional) the height of the region to pixelate
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
image.pixelate(10);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
## convolute
|
||||
|
||||
Applies a convolution kernel to the image or a region
|
||||
|
||||
- @param {array} kernel the convolution kernel
|
||||
- @param {number} x (optional) the x position of the region to apply convolution to
|
||||
- @param {number} y (optional) the y position of the region to apply convolution to
|
||||
- @param {number} w (optional) the width of the region to apply convolution to
|
||||
- @param {number} h (optional) the height of the region to apply convolution to
|
||||
- @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
|
||||
```js
|
||||
import jimp from "jimp";
|
||||
|
||||
async function main() {
|
||||
const image = await jimp.read("test/image.png");
|
||||
|
||||
// make me better
|
||||
image.pixelate(kernal);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
import tinyColor from "tinycolor2";
|
||||
import { throwError, isNodePattern } from "@jimp/utils";
|
||||
function applyKernel(im, kernel, x, y) {
|
||||
const value = [0, 0, 0];
|
||||
const size = (kernel.length - 1) / 2;
|
||||
for (let kx = 0; kx < kernel.length; kx += 1) {
|
||||
for (let ky = 0; ky < kernel[kx].length; ky += 1) {
|
||||
const idx = im.getPixelIndex(x + kx - size, y + ky - size);
|
||||
value[0] += im.bitmap.data[idx] * kernel[kx][ky];
|
||||
value[1] += im.bitmap.data[idx + 1] * kernel[kx][ky];
|
||||
value[2] += im.bitmap.data[idx + 2] * kernel[kx][ky];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
const isDef = v => typeof v !== "undefined" && v !== null;
|
||||
function greyscale(cb) {
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
const grey = parseInt(0.2126 * this.bitmap.data[idx] + 0.7152 * this.bitmap.data[idx + 1] + 0.0722 * this.bitmap.data[idx + 2], 10);
|
||||
this.bitmap.data[idx] = grey;
|
||||
this.bitmap.data[idx + 1] = grey;
|
||||
this.bitmap.data[idx + 2] = grey;
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
function mix(clr, clr2) {
|
||||
let p = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 50;
|
||||
return {
|
||||
r: (clr2.r - clr.r) * (p / 100) + clr.r,
|
||||
g: (clr2.g - clr.g) * (p / 100) + clr.g,
|
||||
b: (clr2.b - clr.b) * (p / 100) + clr.b
|
||||
};
|
||||
}
|
||||
function colorFn(actions, cb) {
|
||||
if (!actions || !Array.isArray(actions)) {
|
||||
return throwError.call(this, "actions must be an array", cb);
|
||||
}
|
||||
actions = actions.map(action => {
|
||||
if (action.apply === "xor" || action.apply === "mix") {
|
||||
action.params[0] = tinyColor(action.params[0]).toRgb();
|
||||
}
|
||||
return action;
|
||||
});
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, (x, y, idx) => {
|
||||
let clr = {
|
||||
r: this.bitmap.data[idx],
|
||||
g: this.bitmap.data[idx + 1],
|
||||
b: this.bitmap.data[idx + 2]
|
||||
};
|
||||
const colorModifier = (i, amount) => this.constructor.limit255(clr[i] + amount);
|
||||
actions.forEach(action => {
|
||||
if (action.apply === "mix") {
|
||||
clr = mix(clr, action.params[0], action.params[1]);
|
||||
} else if (action.apply === "tint") {
|
||||
clr = mix(clr, {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255
|
||||
}, action.params[0]);
|
||||
} else if (action.apply === "shade") {
|
||||
clr = mix(clr, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0
|
||||
}, action.params[0]);
|
||||
} else if (action.apply === "xor") {
|
||||
clr = {
|
||||
r: clr.r ^ action.params[0].r,
|
||||
g: clr.g ^ action.params[0].g,
|
||||
b: clr.b ^ action.params[0].b
|
||||
};
|
||||
} else if (action.apply === "red") {
|
||||
clr.r = colorModifier("r", action.params[0]);
|
||||
} else if (action.apply === "green") {
|
||||
clr.g = colorModifier("g", action.params[0]);
|
||||
} else if (action.apply === "blue") {
|
||||
clr.b = colorModifier("b", action.params[0]);
|
||||
} else {
|
||||
if (action.apply === "hue") {
|
||||
action.apply = "spin";
|
||||
}
|
||||
clr = tinyColor(clr);
|
||||
if (!clr[action.apply]) {
|
||||
return throwError.call(this, "action " + action.apply + " not supported", cb);
|
||||
}
|
||||
clr = clr[action.apply](...action.params).toRgb();
|
||||
}
|
||||
});
|
||||
this.bitmap.data[idx] = clr.r;
|
||||
this.bitmap.data[idx + 1] = clr.g;
|
||||
this.bitmap.data[idx + 2] = clr.b;
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
export const ColorActionName = Object.freeze({
|
||||
LIGHTEN: "lighten",
|
||||
BRIGHTEN: "brighten",
|
||||
DARKEN: "darken",
|
||||
DESATURATE: "desaturate",
|
||||
SATURATE: "saturate",
|
||||
GREYSCALE: "greyscale",
|
||||
SPIN: "spin",
|
||||
HUE: "hue",
|
||||
MIX: "mix",
|
||||
TINT: "tint",
|
||||
SHADE: "shade",
|
||||
XOR: "xor",
|
||||
RED: "red",
|
||||
GREEN: "green",
|
||||
BLUE: "blue"
|
||||
});
|
||||
export default (() => ({
|
||||
/**
|
||||
* Adjusts the brightness of the image
|
||||
* @param {number} val the amount to adjust the brightness, a number between -1 and +1
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
brightness(val, cb) {
|
||||
if (typeof val !== "number") {
|
||||
return throwError.call(this, "val must be numbers", cb);
|
||||
}
|
||||
if (val < -1 || val > +1) {
|
||||
return throwError.call(this, "val must be a number between -1 and +1", cb);
|
||||
}
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
if (val < 0.0) {
|
||||
this.bitmap.data[idx] *= 1 + val;
|
||||
this.bitmap.data[idx + 1] *= 1 + val;
|
||||
this.bitmap.data[idx + 2] *= 1 + val;
|
||||
} else {
|
||||
this.bitmap.data[idx] += (255 - this.bitmap.data[idx]) * val;
|
||||
this.bitmap.data[idx + 1] += (255 - this.bitmap.data[idx + 1]) * val;
|
||||
this.bitmap.data[idx + 2] += (255 - this.bitmap.data[idx + 2]) * val;
|
||||
}
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Adjusts the contrast of the image
|
||||
* @param {number} val the amount to adjust the contrast, a number between -1 and +1
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
contrast(val, cb) {
|
||||
if (typeof val !== "number") {
|
||||
return throwError.call(this, "val must be numbers", cb);
|
||||
}
|
||||
if (val < -1 || val > +1) {
|
||||
return throwError.call(this, "val must be a number between -1 and +1", cb);
|
||||
}
|
||||
const factor = (val + 1) / (1 - val);
|
||||
function adjust(value) {
|
||||
value = Math.floor(factor * (value - 127) + 127);
|
||||
return value < 0 ? 0 : value > 255 ? 255 : value;
|
||||
}
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
this.bitmap.data[idx] = adjust(this.bitmap.data[idx]);
|
||||
this.bitmap.data[idx + 1] = adjust(this.bitmap.data[idx + 1]);
|
||||
this.bitmap.data[idx + 2] = adjust(this.bitmap.data[idx + 2]);
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Apply a posterize effect
|
||||
* @param {number} n the amount to adjust the contrast, minimum threshold is two
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
posterize(n, cb) {
|
||||
if (typeof n !== "number") {
|
||||
return throwError.call(this, "n must be numbers", cb);
|
||||
}
|
||||
if (n < 2) {
|
||||
n = 2;
|
||||
} // minimum of 2 levels
|
||||
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
this.bitmap.data[idx] = Math.floor(this.bitmap.data[idx] / 255 * (n - 1)) / (n - 1) * 255;
|
||||
this.bitmap.data[idx + 1] = Math.floor(this.bitmap.data[idx + 1] / 255 * (n - 1)) / (n - 1) * 255;
|
||||
this.bitmap.data[idx + 2] = Math.floor(this.bitmap.data[idx + 2] / 255 * (n - 1)) / (n - 1) * 255;
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Removes colour from the image using ITU Rec 709 luminance values
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
greyscale,
|
||||
// Alias of greyscale for our American friends
|
||||
grayscale: greyscale,
|
||||
/**
|
||||
* Multiplies the opacity of each pixel by a factor between 0 and 1
|
||||
* @param {number} f A number, the factor by which to multiply the opacity of each pixel
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
opacity(f, cb) {
|
||||
if (typeof f !== "number") return throwError.call(this, "f must be a number", cb);
|
||||
if (f < 0 || f > 1) return throwError.call(this, "f must be a number from 0 to 1", cb);
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
const v = this.bitmap.data[idx + 3] * f;
|
||||
this.bitmap.data[idx + 3] = v;
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Applies a sepia tone to the image
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
sepia(cb) {
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
let red = this.bitmap.data[idx];
|
||||
let green = this.bitmap.data[idx + 1];
|
||||
let blue = this.bitmap.data[idx + 2];
|
||||
red = red * 0.393 + green * 0.769 + blue * 0.189;
|
||||
green = red * 0.349 + green * 0.686 + blue * 0.168;
|
||||
blue = red * 0.272 + green * 0.534 + blue * 0.131;
|
||||
this.bitmap.data[idx] = red < 255 ? red : 255;
|
||||
this.bitmap.data[idx + 1] = green < 255 ? green : 255;
|
||||
this.bitmap.data[idx + 2] = blue < 255 ? blue : 255;
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Fades each pixel by a factor between 0 and 1
|
||||
* @param {number} f A number from 0 to 1. 0 will haven no effect. 1 will turn the image completely transparent.
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
fade(f, cb) {
|
||||
if (typeof f !== "number") {
|
||||
return throwError.call(this, "f must be a number", cb);
|
||||
}
|
||||
if (f < 0 || f > 1) {
|
||||
return throwError.call(this, "f must be a number from 0 to 1", cb);
|
||||
}
|
||||
|
||||
// this method is an alternative to opacity (which may be deprecated)
|
||||
this.opacity(1 - f);
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Adds each element of the image to its local neighbors, weighted by the kernel
|
||||
* @param {array} kernel a matrix to weight the neighbors sum
|
||||
* @param {number} edgeHandling (optional) define how to sum pixels from outside the border
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
convolution(kernel, edgeHandling, cb) {
|
||||
if (typeof edgeHandling === "function" && typeof cb === "undefined") {
|
||||
cb = edgeHandling;
|
||||
edgeHandling = null;
|
||||
}
|
||||
if (!edgeHandling) {
|
||||
edgeHandling = this.constructor.EDGE_EXTEND;
|
||||
}
|
||||
const newData = Buffer.from(this.bitmap.data);
|
||||
const kRows = kernel.length;
|
||||
const kCols = kernel[0].length;
|
||||
const rowEnd = Math.floor(kRows / 2);
|
||||
const colEnd = Math.floor(kCols / 2);
|
||||
const rowIni = -rowEnd;
|
||||
const colIni = -colEnd;
|
||||
let weight;
|
||||
let rSum;
|
||||
let gSum;
|
||||
let bSum;
|
||||
let ri;
|
||||
let gi;
|
||||
let bi;
|
||||
let xi;
|
||||
let yi;
|
||||
let idxi;
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
bSum = 0;
|
||||
gSum = 0;
|
||||
rSum = 0;
|
||||
for (let row = rowIni; row <= rowEnd; row++) {
|
||||
for (let col = colIni; col <= colEnd; col++) {
|
||||
xi = x + col;
|
||||
yi = y + row;
|
||||
weight = kernel[row + rowEnd][col + colEnd];
|
||||
idxi = this.getPixelIndex(xi, yi, edgeHandling);
|
||||
if (idxi === -1) {
|
||||
bi = 0;
|
||||
gi = 0;
|
||||
ri = 0;
|
||||
} else {
|
||||
ri = this.bitmap.data[idxi + 0];
|
||||
gi = this.bitmap.data[idxi + 1];
|
||||
bi = this.bitmap.data[idxi + 2];
|
||||
}
|
||||
rSum += weight * ri;
|
||||
gSum += weight * gi;
|
||||
bSum += weight * bi;
|
||||
}
|
||||
}
|
||||
if (rSum < 0) {
|
||||
rSum = 0;
|
||||
}
|
||||
if (gSum < 0) {
|
||||
gSum = 0;
|
||||
}
|
||||
if (bSum < 0) {
|
||||
bSum = 0;
|
||||
}
|
||||
if (rSum > 255) {
|
||||
rSum = 255;
|
||||
}
|
||||
if (gSum > 255) {
|
||||
gSum = 255;
|
||||
}
|
||||
if (bSum > 255) {
|
||||
bSum = 255;
|
||||
}
|
||||
newData[idx + 0] = rSum;
|
||||
newData[idx + 1] = gSum;
|
||||
newData[idx + 2] = bSum;
|
||||
});
|
||||
this.bitmap.data = newData;
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Set the alpha channel on every pixel to fully opaque
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
opaque(cb) {
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
|
||||
this.bitmap.data[idx + 3] = 255;
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Pixelates the image or a region
|
||||
* @param {number} size the size of the pixels
|
||||
* @param {number} x (optional) the x position of the region to pixelate
|
||||
* @param {number} y (optional) the y position of the region to pixelate
|
||||
* @param {number} w (optional) the width of the region to pixelate
|
||||
* @param {number} h (optional) the height of the region to pixelate
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
pixelate(size, x, y, w, h, cb) {
|
||||
if (typeof x === "function") {
|
||||
cb = x;
|
||||
h = null;
|
||||
w = null;
|
||||
y = null;
|
||||
x = null;
|
||||
} else {
|
||||
if (typeof size !== "number") {
|
||||
return throwError.call(this, "size must be a number", cb);
|
||||
}
|
||||
if (isDef(x) && typeof x !== "number") {
|
||||
return throwError.call(this, "x must be a number", cb);
|
||||
}
|
||||
if (isDef(y) && typeof y !== "number") {
|
||||
return throwError.call(this, "y must be a number", cb);
|
||||
}
|
||||
if (isDef(w) && typeof w !== "number") {
|
||||
return throwError.call(this, "w must be a number", cb);
|
||||
}
|
||||
if (isDef(h) && typeof h !== "number") {
|
||||
return throwError.call(this, "h must be a number", cb);
|
||||
}
|
||||
}
|
||||
const kernel = [[1 / 16, 2 / 16, 1 / 16], [2 / 16, 4 / 16, 2 / 16], [1 / 16, 2 / 16, 1 / 16]];
|
||||
x = x || 0;
|
||||
y = y || 0;
|
||||
w = isDef(w) ? w : this.bitmap.width - x;
|
||||
h = isDef(h) ? h : this.bitmap.height - y;
|
||||
const source = this.cloneQuiet();
|
||||
this.scanQuiet(x, y, w, h, function (xx, yx, idx) {
|
||||
xx = size * Math.floor(xx / size);
|
||||
yx = size * Math.floor(yx / size);
|
||||
const value = applyKernel(source, kernel, xx, yx);
|
||||
this.bitmap.data[idx] = value[0];
|
||||
this.bitmap.data[idx + 1] = value[1];
|
||||
this.bitmap.data[idx + 2] = value[2];
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Applies a convolution kernel to the image or a region
|
||||
* @param {array} kernel the convolution kernel
|
||||
* @param {number} x (optional) the x position of the region to apply convolution to
|
||||
* @param {number} y (optional) the y position of the region to apply convolution to
|
||||
* @param {number} w (optional) the width of the region to apply convolution to
|
||||
* @param {number} h (optional) the height of the region to apply convolution to
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp} this for chaining of methods
|
||||
*/
|
||||
convolute(kernel, x, y, w, h, cb) {
|
||||
if (!Array.isArray(kernel)) return throwError.call(this, "the kernel must be an array", cb);
|
||||
if (typeof x === "function") {
|
||||
cb = x;
|
||||
x = null;
|
||||
y = null;
|
||||
w = null;
|
||||
h = null;
|
||||
} else {
|
||||
if (isDef(x) && typeof x !== "number") {
|
||||
return throwError.call(this, "x must be a number", cb);
|
||||
}
|
||||
if (isDef(y) && typeof y !== "number") {
|
||||
return throwError.call(this, "y must be a number", cb);
|
||||
}
|
||||
if (isDef(w) && typeof w !== "number") {
|
||||
return throwError.call(this, "w must be a number", cb);
|
||||
}
|
||||
if (isDef(h) && typeof h !== "number") {
|
||||
return throwError.call(this, "h must be a number", cb);
|
||||
}
|
||||
}
|
||||
x = isDef(x) ? x : 0;
|
||||
y = isDef(y) ? y : 0;
|
||||
w = isDef(w) ? w : this.bitmap.width - x;
|
||||
h = isDef(h) ? h : this.bitmap.height - y;
|
||||
const source = this.cloneQuiet();
|
||||
this.scanQuiet(x, y, w, h, function (xx, yx, idx) {
|
||||
const value = applyKernel(source, kernel, xx, yx);
|
||||
this.bitmap.data[idx] = this.constructor.limit255(value[0]);
|
||||
this.bitmap.data[idx + 1] = this.constructor.limit255(value[1]);
|
||||
this.bitmap.data[idx + 2] = this.constructor.limit255(value[2]);
|
||||
});
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* Apply multiple color modification rules
|
||||
* @param {array} actions list of color modification rules, in following format: { apply: '<rule-name>', params: [ <rule-parameters> ] }
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
color: colorFn,
|
||||
colour: colorFn
|
||||
}));
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+64
@@ -0,0 +1,64 @@
|
||||
import { ImageCallback } from "@jimp/core";
|
||||
|
||||
export enum ColorActionName {
|
||||
LIGHTEN = "lighten",
|
||||
BRIGHTEN = "brighten",
|
||||
DARKEN = "darken",
|
||||
DESATURATE = "desaturate",
|
||||
SATURATE = "saturate",
|
||||
GREYSCALE = "greyscale",
|
||||
SPIN = "spin",
|
||||
HUE = "hue",
|
||||
MIX = "mix",
|
||||
TINT = "tint",
|
||||
SHADE = "shade",
|
||||
XOR = "xor",
|
||||
RED = "red",
|
||||
GREEN = "green",
|
||||
BLUE = "blue",
|
||||
}
|
||||
|
||||
type ColorAction = {
|
||||
apply: ColorActionName;
|
||||
params: any;
|
||||
};
|
||||
|
||||
interface Color {
|
||||
brightness(val: number, cb?: ImageCallback<this>): this;
|
||||
contrast(val: number, cb?: ImageCallback<this>): this;
|
||||
posterize(n: number, cb?: ImageCallback<this>): this;
|
||||
greyscale(cb?: ImageCallback<this>): this;
|
||||
grayscale(cb?: ImageCallback<this>): this;
|
||||
opacity(f: number, cb?: ImageCallback<this>): this;
|
||||
sepia(cb?: ImageCallback<this>): this;
|
||||
fade(f: number, cb?: ImageCallback<this>): this;
|
||||
convolution(kernel: number[][], cb?: ImageCallback<this>): this;
|
||||
convolution<T>(
|
||||
kernel: number[][],
|
||||
edgeHandling: number,
|
||||
cb?: ImageCallback<this>
|
||||
): this;
|
||||
opaque(cb?: ImageCallback<this>): this;
|
||||
pixelate(size: number, cb?: ImageCallback<this>): this;
|
||||
pixelate(
|
||||
size: number,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
cb?: ImageCallback<this>
|
||||
): this;
|
||||
convolute(kernel: number[][], cb?: ImageCallback<this>): this;
|
||||
convolute(
|
||||
kernel: number[][],
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
cb?: ImageCallback<this>
|
||||
): this;
|
||||
color(actions: ColorAction[], cb?: ImageCallback<this>): this;
|
||||
colour(actions: ColorAction[], cb?: ImageCallback<this>): this;
|
||||
}
|
||||
|
||||
export default function (): Color;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@jimp/plugin-color",
|
||||
"version": "0.22.12",
|
||||
"description": "Bitmap manipulation to adjust the color in an image.",
|
||||
"main": "dist/index.js",
|
||||
"module": "es/index.js",
|
||||
"repository": "jimp-dev/jimp",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"test": "cross-env BABEL_ENV=test mocha --require @babel/register --recursive test --extension js",
|
||||
"test:watch": "npm run test -- --reporter min --watch",
|
||||
"test:coverage": "nyc npm run test",
|
||||
"build": "npm run build:node:production && npm run build:module",
|
||||
"build:watch": "npm run build:node:debug -- -- --watch --verbose",
|
||||
"build:debug": "npm run build:node:debug",
|
||||
"build:module": "cross-env BABEL_ENV=module babel src -d es --source-maps --config-file ../../babel.config.js",
|
||||
"build:node": "babel src -d dist --source-maps --config-file ../../babel.config.js",
|
||||
"build:node:debug": "cross-env BABEL_ENV=development npm run build:node",
|
||||
"build:node:production": "cross-env BABEL_ENV=production npm run build:node"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/utils": "^0.22.12",
|
||||
"tinycolor2": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jimp/custom": "^0.22.12",
|
||||
"@jimp/test-utils": "^0.22.12",
|
||||
"@jimp/types": "^0.22.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@jimp/custom": ">=0.3.5"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHead": "a4a8d6364bbf97629749e196f3b0a4c94c9a7abc"
|
||||
}
|
||||
+646
@@ -0,0 +1,646 @@
|
||||
import tinyColor from "tinycolor2";
|
||||
import { throwError, isNodePattern } from "@jimp/utils";
|
||||
|
||||
function applyKernel(im, kernel, x, y) {
|
||||
const value = [0, 0, 0];
|
||||
const size = (kernel.length - 1) / 2;
|
||||
|
||||
for (let kx = 0; kx < kernel.length; kx += 1) {
|
||||
for (let ky = 0; ky < kernel[kx].length; ky += 1) {
|
||||
const idx = im.getPixelIndex(x + kx - size, y + ky - size);
|
||||
|
||||
value[0] += im.bitmap.data[idx] * kernel[kx][ky];
|
||||
value[1] += im.bitmap.data[idx + 1] * kernel[kx][ky];
|
||||
value[2] += im.bitmap.data[idx + 2] * kernel[kx][ky];
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const isDef = (v) => typeof v !== "undefined" && v !== null;
|
||||
|
||||
function greyscale(cb) {
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
const grey = parseInt(
|
||||
0.2126 * this.bitmap.data[idx] +
|
||||
0.7152 * this.bitmap.data[idx + 1] +
|
||||
0.0722 * this.bitmap.data[idx + 2],
|
||||
10
|
||||
);
|
||||
|
||||
this.bitmap.data[idx] = grey;
|
||||
this.bitmap.data[idx + 1] = grey;
|
||||
this.bitmap.data[idx + 2] = grey;
|
||||
}
|
||||
);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
function mix(clr, clr2, p = 50) {
|
||||
return {
|
||||
r: (clr2.r - clr.r) * (p / 100) + clr.r,
|
||||
g: (clr2.g - clr.g) * (p / 100) + clr.g,
|
||||
b: (clr2.b - clr.b) * (p / 100) + clr.b,
|
||||
};
|
||||
}
|
||||
|
||||
function colorFn(actions, cb) {
|
||||
if (!actions || !Array.isArray(actions)) {
|
||||
return throwError.call(this, "actions must be an array", cb);
|
||||
}
|
||||
|
||||
actions = actions.map((action) => {
|
||||
if (action.apply === "xor" || action.apply === "mix") {
|
||||
action.params[0] = tinyColor(action.params[0]).toRgb();
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
|
||||
this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, (x, y, idx) => {
|
||||
let clr = {
|
||||
r: this.bitmap.data[idx],
|
||||
g: this.bitmap.data[idx + 1],
|
||||
b: this.bitmap.data[idx + 2],
|
||||
};
|
||||
|
||||
const colorModifier = (i, amount) =>
|
||||
this.constructor.limit255(clr[i] + amount);
|
||||
|
||||
actions.forEach((action) => {
|
||||
if (action.apply === "mix") {
|
||||
clr = mix(clr, action.params[0], action.params[1]);
|
||||
} else if (action.apply === "tint") {
|
||||
clr = mix(clr, { r: 255, g: 255, b: 255 }, action.params[0]);
|
||||
} else if (action.apply === "shade") {
|
||||
clr = mix(clr, { r: 0, g: 0, b: 0 }, action.params[0]);
|
||||
} else if (action.apply === "xor") {
|
||||
clr = {
|
||||
r: clr.r ^ action.params[0].r,
|
||||
g: clr.g ^ action.params[0].g,
|
||||
b: clr.b ^ action.params[0].b,
|
||||
};
|
||||
} else if (action.apply === "red") {
|
||||
clr.r = colorModifier("r", action.params[0]);
|
||||
} else if (action.apply === "green") {
|
||||
clr.g = colorModifier("g", action.params[0]);
|
||||
} else if (action.apply === "blue") {
|
||||
clr.b = colorModifier("b", action.params[0]);
|
||||
} else {
|
||||
if (action.apply === "hue") {
|
||||
action.apply = "spin";
|
||||
}
|
||||
|
||||
clr = tinyColor(clr);
|
||||
|
||||
if (!clr[action.apply]) {
|
||||
return throwError.call(
|
||||
this,
|
||||
"action " + action.apply + " not supported",
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
clr = clr[action.apply](...action.params).toRgb();
|
||||
}
|
||||
});
|
||||
|
||||
this.bitmap.data[idx] = clr.r;
|
||||
this.bitmap.data[idx + 1] = clr.g;
|
||||
this.bitmap.data[idx + 2] = clr.b;
|
||||
});
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
export const ColorActionName = Object.freeze({
|
||||
LIGHTEN: "lighten",
|
||||
BRIGHTEN: "brighten",
|
||||
DARKEN: "darken",
|
||||
DESATURATE: "desaturate",
|
||||
SATURATE: "saturate",
|
||||
GREYSCALE: "greyscale",
|
||||
SPIN: "spin",
|
||||
HUE: "hue",
|
||||
MIX: "mix",
|
||||
TINT: "tint",
|
||||
SHADE: "shade",
|
||||
XOR: "xor",
|
||||
RED: "red",
|
||||
GREEN: "green",
|
||||
BLUE: "blue",
|
||||
});
|
||||
|
||||
export default () => ({
|
||||
/**
|
||||
* Adjusts the brightness of the image
|
||||
* @param {number} val the amount to adjust the brightness, a number between -1 and +1
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
brightness(val, cb) {
|
||||
if (typeof val !== "number") {
|
||||
return throwError.call(this, "val must be numbers", cb);
|
||||
}
|
||||
|
||||
if (val < -1 || val > +1) {
|
||||
return throwError.call(
|
||||
this,
|
||||
"val must be a number between -1 and +1",
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
if (val < 0.0) {
|
||||
this.bitmap.data[idx] *= 1 + val;
|
||||
this.bitmap.data[idx + 1] *= 1 + val;
|
||||
this.bitmap.data[idx + 2] *= 1 + val;
|
||||
} else {
|
||||
this.bitmap.data[idx] += (255 - this.bitmap.data[idx]) * val;
|
||||
this.bitmap.data[idx + 1] += (255 - this.bitmap.data[idx + 1]) * val;
|
||||
this.bitmap.data[idx + 2] += (255 - this.bitmap.data[idx + 2]) * val;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adjusts the contrast of the image
|
||||
* @param {number} val the amount to adjust the contrast, a number between -1 and +1
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
contrast(val, cb) {
|
||||
if (typeof val !== "number") {
|
||||
return throwError.call(this, "val must be numbers", cb);
|
||||
}
|
||||
|
||||
if (val < -1 || val > +1) {
|
||||
return throwError.call(
|
||||
this,
|
||||
"val must be a number between -1 and +1",
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
const factor = (val + 1) / (1 - val);
|
||||
|
||||
function adjust(value) {
|
||||
value = Math.floor(factor * (value - 127) + 127);
|
||||
|
||||
return value < 0 ? 0 : value > 255 ? 255 : value;
|
||||
}
|
||||
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
this.bitmap.data[idx] = adjust(this.bitmap.data[idx]);
|
||||
this.bitmap.data[idx + 1] = adjust(this.bitmap.data[idx + 1]);
|
||||
this.bitmap.data[idx + 2] = adjust(this.bitmap.data[idx + 2]);
|
||||
}
|
||||
);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply a posterize effect
|
||||
* @param {number} n the amount to adjust the contrast, minimum threshold is two
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
posterize(n, cb) {
|
||||
if (typeof n !== "number") {
|
||||
return throwError.call(this, "n must be numbers", cb);
|
||||
}
|
||||
|
||||
if (n < 2) {
|
||||
n = 2;
|
||||
} // minimum of 2 levels
|
||||
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
this.bitmap.data[idx] =
|
||||
(Math.floor((this.bitmap.data[idx] / 255) * (n - 1)) / (n - 1)) * 255;
|
||||
this.bitmap.data[idx + 1] =
|
||||
(Math.floor((this.bitmap.data[idx + 1] / 255) * (n - 1)) / (n - 1)) *
|
||||
255;
|
||||
this.bitmap.data[idx + 2] =
|
||||
(Math.floor((this.bitmap.data[idx + 2] / 255) * (n - 1)) / (n - 1)) *
|
||||
255;
|
||||
}
|
||||
);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes colour from the image using ITU Rec 709 luminance values
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
greyscale,
|
||||
|
||||
// Alias of greyscale for our American friends
|
||||
grayscale: greyscale,
|
||||
|
||||
/**
|
||||
* Multiplies the opacity of each pixel by a factor between 0 and 1
|
||||
* @param {number} f A number, the factor by which to multiply the opacity of each pixel
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
opacity(f, cb) {
|
||||
if (typeof f !== "number")
|
||||
return throwError.call(this, "f must be a number", cb);
|
||||
if (f < 0 || f > 1)
|
||||
return throwError.call(this, "f must be a number from 0 to 1", cb);
|
||||
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
const v = this.bitmap.data[idx + 3] * f;
|
||||
this.bitmap.data[idx + 3] = v;
|
||||
}
|
||||
);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies a sepia tone to the image
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
sepia(cb) {
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
let red = this.bitmap.data[idx];
|
||||
let green = this.bitmap.data[idx + 1];
|
||||
let blue = this.bitmap.data[idx + 2];
|
||||
|
||||
red = red * 0.393 + green * 0.769 + blue * 0.189;
|
||||
green = red * 0.349 + green * 0.686 + blue * 0.168;
|
||||
blue = red * 0.272 + green * 0.534 + blue * 0.131;
|
||||
|
||||
this.bitmap.data[idx] = red < 255 ? red : 255;
|
||||
this.bitmap.data[idx + 1] = green < 255 ? green : 255;
|
||||
this.bitmap.data[idx + 2] = blue < 255 ? blue : 255;
|
||||
}
|
||||
);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fades each pixel by a factor between 0 and 1
|
||||
* @param {number} f A number from 0 to 1. 0 will haven no effect. 1 will turn the image completely transparent.
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
fade(f, cb) {
|
||||
if (typeof f !== "number") {
|
||||
return throwError.call(this, "f must be a number", cb);
|
||||
}
|
||||
|
||||
if (f < 0 || f > 1) {
|
||||
return throwError.call(this, "f must be a number from 0 to 1", cb);
|
||||
}
|
||||
|
||||
// this method is an alternative to opacity (which may be deprecated)
|
||||
this.opacity(1 - f);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds each element of the image to its local neighbors, weighted by the kernel
|
||||
* @param {array} kernel a matrix to weight the neighbors sum
|
||||
* @param {number} edgeHandling (optional) define how to sum pixels from outside the border
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
convolution(kernel, edgeHandling, cb) {
|
||||
if (typeof edgeHandling === "function" && typeof cb === "undefined") {
|
||||
cb = edgeHandling;
|
||||
edgeHandling = null;
|
||||
}
|
||||
|
||||
if (!edgeHandling) {
|
||||
edgeHandling = this.constructor.EDGE_EXTEND;
|
||||
}
|
||||
|
||||
const newData = Buffer.from(this.bitmap.data);
|
||||
const kRows = kernel.length;
|
||||
const kCols = kernel[0].length;
|
||||
const rowEnd = Math.floor(kRows / 2);
|
||||
const colEnd = Math.floor(kCols / 2);
|
||||
const rowIni = -rowEnd;
|
||||
const colIni = -colEnd;
|
||||
|
||||
let weight;
|
||||
let rSum;
|
||||
let gSum;
|
||||
let bSum;
|
||||
let ri;
|
||||
let gi;
|
||||
let bi;
|
||||
let xi;
|
||||
let yi;
|
||||
let idxi;
|
||||
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
bSum = 0;
|
||||
gSum = 0;
|
||||
rSum = 0;
|
||||
|
||||
for (let row = rowIni; row <= rowEnd; row++) {
|
||||
for (let col = colIni; col <= colEnd; col++) {
|
||||
xi = x + col;
|
||||
yi = y + row;
|
||||
weight = kernel[row + rowEnd][col + colEnd];
|
||||
idxi = this.getPixelIndex(xi, yi, edgeHandling);
|
||||
|
||||
if (idxi === -1) {
|
||||
bi = 0;
|
||||
gi = 0;
|
||||
ri = 0;
|
||||
} else {
|
||||
ri = this.bitmap.data[idxi + 0];
|
||||
gi = this.bitmap.data[idxi + 1];
|
||||
bi = this.bitmap.data[idxi + 2];
|
||||
}
|
||||
|
||||
rSum += weight * ri;
|
||||
gSum += weight * gi;
|
||||
bSum += weight * bi;
|
||||
}
|
||||
}
|
||||
|
||||
if (rSum < 0) {
|
||||
rSum = 0;
|
||||
}
|
||||
|
||||
if (gSum < 0) {
|
||||
gSum = 0;
|
||||
}
|
||||
|
||||
if (bSum < 0) {
|
||||
bSum = 0;
|
||||
}
|
||||
|
||||
if (rSum > 255) {
|
||||
rSum = 255;
|
||||
}
|
||||
|
||||
if (gSum > 255) {
|
||||
gSum = 255;
|
||||
}
|
||||
|
||||
if (bSum > 255) {
|
||||
bSum = 255;
|
||||
}
|
||||
|
||||
newData[idx + 0] = rSum;
|
||||
newData[idx + 1] = gSum;
|
||||
newData[idx + 2] = bSum;
|
||||
}
|
||||
);
|
||||
|
||||
this.bitmap.data = newData;
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the alpha channel on every pixel to fully opaque
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
opaque(cb) {
|
||||
this.scanQuiet(
|
||||
0,
|
||||
0,
|
||||
this.bitmap.width,
|
||||
this.bitmap.height,
|
||||
function (x, y, idx) {
|
||||
this.bitmap.data[idx + 3] = 255;
|
||||
}
|
||||
);
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Pixelates the image or a region
|
||||
* @param {number} size the size of the pixels
|
||||
* @param {number} x (optional) the x position of the region to pixelate
|
||||
* @param {number} y (optional) the y position of the region to pixelate
|
||||
* @param {number} w (optional) the width of the region to pixelate
|
||||
* @param {number} h (optional) the height of the region to pixelate
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
pixelate(size, x, y, w, h, cb) {
|
||||
if (typeof x === "function") {
|
||||
cb = x;
|
||||
h = null;
|
||||
w = null;
|
||||
y = null;
|
||||
x = null;
|
||||
} else {
|
||||
if (typeof size !== "number") {
|
||||
return throwError.call(this, "size must be a number", cb);
|
||||
}
|
||||
|
||||
if (isDef(x) && typeof x !== "number") {
|
||||
return throwError.call(this, "x must be a number", cb);
|
||||
}
|
||||
|
||||
if (isDef(y) && typeof y !== "number") {
|
||||
return throwError.call(this, "y must be a number", cb);
|
||||
}
|
||||
|
||||
if (isDef(w) && typeof w !== "number") {
|
||||
return throwError.call(this, "w must be a number", cb);
|
||||
}
|
||||
|
||||
if (isDef(h) && typeof h !== "number") {
|
||||
return throwError.call(this, "h must be a number", cb);
|
||||
}
|
||||
}
|
||||
|
||||
const kernel = [
|
||||
[1 / 16, 2 / 16, 1 / 16],
|
||||
[2 / 16, 4 / 16, 2 / 16],
|
||||
[1 / 16, 2 / 16, 1 / 16],
|
||||
];
|
||||
|
||||
x = x || 0;
|
||||
y = y || 0;
|
||||
w = isDef(w) ? w : this.bitmap.width - x;
|
||||
h = isDef(h) ? h : this.bitmap.height - y;
|
||||
|
||||
const source = this.cloneQuiet();
|
||||
|
||||
this.scanQuiet(x, y, w, h, function (xx, yx, idx) {
|
||||
xx = size * Math.floor(xx / size);
|
||||
yx = size * Math.floor(yx / size);
|
||||
|
||||
const value = applyKernel(source, kernel, xx, yx);
|
||||
|
||||
this.bitmap.data[idx] = value[0];
|
||||
this.bitmap.data[idx + 1] = value[1];
|
||||
this.bitmap.data[idx + 2] = value[2];
|
||||
});
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies a convolution kernel to the image or a region
|
||||
* @param {array} kernel the convolution kernel
|
||||
* @param {number} x (optional) the x position of the region to apply convolution to
|
||||
* @param {number} y (optional) the y position of the region to apply convolution to
|
||||
* @param {number} w (optional) the width of the region to apply convolution to
|
||||
* @param {number} h (optional) the height of the region to apply convolution to
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp} this for chaining of methods
|
||||
*/
|
||||
convolute(kernel, x, y, w, h, cb) {
|
||||
if (!Array.isArray(kernel))
|
||||
return throwError.call(this, "the kernel must be an array", cb);
|
||||
|
||||
if (typeof x === "function") {
|
||||
cb = x;
|
||||
x = null;
|
||||
y = null;
|
||||
w = null;
|
||||
h = null;
|
||||
} else {
|
||||
if (isDef(x) && typeof x !== "number") {
|
||||
return throwError.call(this, "x must be a number", cb);
|
||||
}
|
||||
|
||||
if (isDef(y) && typeof y !== "number") {
|
||||
return throwError.call(this, "y must be a number", cb);
|
||||
}
|
||||
|
||||
if (isDef(w) && typeof w !== "number") {
|
||||
return throwError.call(this, "w must be a number", cb);
|
||||
}
|
||||
|
||||
if (isDef(h) && typeof h !== "number") {
|
||||
return throwError.call(this, "h must be a number", cb);
|
||||
}
|
||||
}
|
||||
|
||||
x = isDef(x) ? x : 0;
|
||||
y = isDef(y) ? y : 0;
|
||||
w = isDef(w) ? w : this.bitmap.width - x;
|
||||
h = isDef(h) ? h : this.bitmap.height - y;
|
||||
|
||||
const source = this.cloneQuiet();
|
||||
|
||||
this.scanQuiet(x, y, w, h, function (xx, yx, idx) {
|
||||
const value = applyKernel(source, kernel, xx, yx);
|
||||
|
||||
this.bitmap.data[idx] = this.constructor.limit255(value[0]);
|
||||
this.bitmap.data[idx + 1] = this.constructor.limit255(value[1]);
|
||||
this.bitmap.data[idx + 2] = this.constructor.limit255(value[2]);
|
||||
});
|
||||
|
||||
if (isNodePattern(cb)) {
|
||||
cb.call(this, null, this);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply multiple color modification rules
|
||||
* @param {array} actions list of color modification rules, in following format: { apply: '<rule-name>', params: [ <rule-parameters> ] }
|
||||
* @param {function(Error, Jimp)} cb (optional) a callback for when complete
|
||||
* @returns {Jimp }this for chaining of methods
|
||||
*/
|
||||
color: colorFn,
|
||||
colour: colorFn,
|
||||
});
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { Jimp, donutJGD } from "@jimp/test-utils";
|
||||
import configure from "@jimp/custom";
|
||||
|
||||
import color from "../src";
|
||||
import { expectToBeJGD } from "@jimp/test-utils/src";
|
||||
|
||||
const jimp = configure({ plugins: [color] }, Jimp);
|
||||
|
||||
describe("canvas color transformation", () => {
|
||||
const redDonutJGD = donutJGD(0x00000000, 0xff000088, 0xff0000ff);
|
||||
|
||||
it("can apply more than one color transformation", async () => {
|
||||
const image = await jimp.read(redDonutJGD);
|
||||
const newJGD = image
|
||||
.color([
|
||||
{ apply: "hue", params: [-180] },
|
||||
{ apply: "lighten", params: [25] },
|
||||
])
|
||||
.getJGDSync();
|
||||
|
||||
expectToBeJGD(newJGD, donutJGD(0x40404000, 0x80ffff88, 0x80ffffff));
|
||||
});
|
||||
|
||||
it("lighten", async () => {
|
||||
const image = await jimp.read(redDonutJGD);
|
||||
|
||||
expectToBeJGD(
|
||||
image.color([{ apply: "lighten", params: [25] }]).getJGDSync(),
|
||||
donutJGD(0x40404000, 0xff808088, 0xff8080ff)
|
||||
);
|
||||
});
|
||||
|
||||
it("brighten", async () => {
|
||||
const image = await jimp.read(redDonutJGD);
|
||||
|
||||
expectToBeJGD(
|
||||
image.color([{ apply: "brighten", params: [25] }]).getJGDSync(),
|
||||
donutJGD(0x40404000, 0xff404088, 0xff4040ff)
|
||||
);
|
||||
});
|
||||
|
||||
it("spin hue", async () => {
|
||||
const image = await jimp.read(redDonutJGD);
|
||||
|
||||
expectToBeJGD(
|
||||
image.color([{ apply: "hue", params: [150] }]).getJGDSync(),
|
||||
donutJGD(0x00000000, 0x00ff8088, 0x00ff80ff)
|
||||
);
|
||||
});
|
||||
});
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
import { Jimp, mkJGD, getTestDir } from "@jimp/test-utils";
|
||||
import configure from "@jimp/custom";
|
||||
import types from "@jimp/types";
|
||||
import expect from "@storybook/expect";
|
||||
|
||||
import color from "../src";
|
||||
import { expectToBeJGD } from "@jimp/test-utils/src";
|
||||
|
||||
const jimp = configure({ types: [types], plugins: [color] }, Jimp);
|
||||
|
||||
describe("Convolution", function () {
|
||||
this.timeout(15000);
|
||||
|
||||
const imgs = [
|
||||
jimp.read(
|
||||
mkJGD(
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22888822",
|
||||
"22888822",
|
||||
"22888822",
|
||||
"22888822",
|
||||
"22222222",
|
||||
"22222222"
|
||||
)
|
||||
),
|
||||
jimp.read(
|
||||
mkJGD(
|
||||
"88222222",
|
||||
"88222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222"
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
let imgMid;
|
||||
let imgTopLeft; // stores the Jimp instances of the JGD images above.
|
||||
|
||||
before((done) => {
|
||||
Promise.all(imgs)
|
||||
.then((imgs) => {
|
||||
imgMid = imgs[0];
|
||||
imgTopLeft = imgs[1];
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
const sharpM = [
|
||||
[-1, -1, 0],
|
||||
[-1, 1, 1],
|
||||
[0, 1, 1],
|
||||
];
|
||||
|
||||
const blurM = [
|
||||
[1 / 9, 1 / 9, 1 / 9],
|
||||
[1 / 9, 1 / 9, 1 / 9],
|
||||
[1 / 9, 1 / 9, 1 / 9],
|
||||
];
|
||||
|
||||
it("3x3 sharp matrix on EDGE_EXTEND", (done) => {
|
||||
expectToBeJGD(
|
||||
imgMid.clone().convolution(sharpM).getJGDSync(),
|
||||
mkJGD(
|
||||
"22222222",
|
||||
"28EEE822",
|
||||
"2EFFF802",
|
||||
"2EF88002",
|
||||
"2EF88002",
|
||||
"28800002",
|
||||
"22000002",
|
||||
"22222222"
|
||||
)
|
||||
);
|
||||
|
||||
expectToBeJGD(
|
||||
imgTopLeft.clone().convolution(sharpM).getJGDSync(),
|
||||
mkJGD(
|
||||
"80022222",
|
||||
"00022222",
|
||||
"00022222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222"
|
||||
)
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it("3x3 sharp matrix on EDGE_WRAP", (done) => {
|
||||
expectToBeJGD(
|
||||
imgMid.clone().convolution(sharpM, jimp.EDGE_WRAP).getJGDSync(),
|
||||
mkJGD(
|
||||
"22222222",
|
||||
"28EEE822",
|
||||
"2EFFF802",
|
||||
"2EF88002",
|
||||
"2EF88002",
|
||||
"28800002",
|
||||
"22000002",
|
||||
"22222222"
|
||||
)
|
||||
);
|
||||
|
||||
expectToBeJGD(
|
||||
imgTopLeft.clone().convolution(sharpM, jimp.EDGE_WRAP).getJGDSync(),
|
||||
mkJGD(
|
||||
"F802222E",
|
||||
"80022228",
|
||||
"00022222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"22222222",
|
||||
"E8222228"
|
||||
)
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it("3x3 sharp matrix on EDGE_CROP", (done) => {
|
||||
expectToBeJGD(
|
||||
imgMid.clone().convolution(sharpM, jimp.EDGE_CROP).getJGDSync(),
|
||||
mkJGD(
|
||||
"86666662",
|
||||
"68EEE820",
|
||||
"6EFFF800",
|
||||
"6EF88000",
|
||||
"6EF88000",
|
||||
"68800000",
|
||||
"62000000",
|
||||
"20000000"
|
||||
)
|
||||
);
|
||||
|
||||
expectToBeJGD(
|
||||
imgTopLeft.clone().convolution(sharpM, jimp.EDGE_CROP).getJGDSync(),
|
||||
mkJGD(
|
||||
"FC066662",
|
||||
"C0022220",
|
||||
"00022220",
|
||||
"62222220",
|
||||
"62222220",
|
||||
"62222220",
|
||||
"62222220",
|
||||
"20000000"
|
||||
)
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it("3x3 box blur matrix using convolute", async () => {
|
||||
const expectedImg = await jimp.read(
|
||||
getTestDir(__dirname) + "/images/tiles-blurred.png"
|
||||
);
|
||||
|
||||
const image = await jimp.read(getTestDir(__dirname) + "/images/tiles.jpg");
|
||||
|
||||
expect(image.convolute(blurM).bitmap.data).toEqual(expectedImg.bitmap.data);
|
||||
});
|
||||
|
||||
it("new pixel value is greater than 255", async () => {
|
||||
const expectedImg = await jimp.read(
|
||||
getTestDir(__dirname) + "/images/qr-convoluted.png"
|
||||
);
|
||||
const image = await jimp.read(getTestDir(__dirname) + "/images/qr.jpg");
|
||||
|
||||
expect(
|
||||
image.convolution([
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0],
|
||||
[0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
]).bitmap.data
|
||||
).toEqual(expectedImg.bitmap.data);
|
||||
});
|
||||
});
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
Reference in New Issue
Block a user