Skip to content

Commit 67efc0a

Browse files
committed
let recolor take the colors from another image
1 parent 875d1a3 commit 67efc0a

3 files changed

Lines changed: 41 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### New
66

77
- **`Image.linearGradient(width, height, { stops, angle })`** and **`Image.radialGradient(width, height, { stops, x, y, radius })`**: images with a gradient, with the angle like CSS' `linear-gradient()`. `stops` are positions and colors like `Colors.gradient()` takes them, or just the colors, evenly spaced.
8+
- **`image.recolor(otherImage)`**: takes the colors from another image of the same size and keeps the alpha. With a gradient it fills a shape, e.g. gradient text: `text.recolor(Image.linearGradient(text.width, text.height, { stops: [0x22d3eeff, 0xfb923cff], angle: 90 }))`.
89

910
## 3.18.0
1011

‎src/image.ts‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,9 +1508,31 @@ export class Image {
15081508
* or tints it: `image.recolor(0xffffffff)` makes every pixel white, keeping its transparency
15091509
* @param color The color to use, its alpha is ignored
15101510
*/
1511-
recolor(color: Color): this {
1511+
recolor(color: Color): this;
1512+
/**
1513+
* Sets the RGB of every pixel to the RGB of the same pixel of another image, keeping each pixel's own alpha. With a
1514+
* gradient this fills a shape with it, e.g. gradient text:
1515+
* `text.recolor(Image.linearGradient(text.width, text.height, { stops: [0x22d3eeff, 0xfb923cff], angle: 90 }))`
1516+
* @param colors The image to take the colors from, it has to have the same size. Its alpha is ignored
1517+
*/
1518+
recolor(colors: Image): this;
1519+
recolor(colorOrImage: Color | Image): this {
1520+
if (colorOrImage instanceof Image) {
1521+
const colors = colorOrImage;
1522+
if (colors.width !== this.width || colors.height !== this.height)
1523+
throw new RangeError(`The image to take the colors from has to be ${this.width}x${this.height}, not ${colors.width}x${colors.height}`);
1524+
// (the gain map's resolution gets the colors at its resolution)
1525+
if (this.__gain_map__) return this.__recolor__(image => image.recolor(image.width === colors.width && image.height === colors.height ? colors : colors.clone().resize(image.width, image.height)));
1526+
const from = colors.bitmap, to = this.bitmap;
1527+
for (let i = 0; i < to.length; i += 4) {
1528+
to[i] = from[i];
1529+
to[i + 1] = from[i + 1];
1530+
to[i + 2] = from[i + 2];
1531+
}
1532+
return this;
1533+
}
1534+
const color = checkColor(colorOrImage);
15121535
if (this.__gain_map__) return this.__recolor__(image => image.recolor(color));
1513-
color = checkColor(color);
15141536
const r = color >>> 24, g = color >> 16 & 0xff, b = color >> 8 & 0xff;
15151537
for (let i = 0; i < this.bitmap.length; i += 4) {
15161538
this.bitmap[i] = r;

‎tests/regressions.js‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,8 +613,9 @@ const rgba = (image, x, y) => Image.colorToRGBA(image.getPixelAt(x, y));
613613
assert(Buffer.from(await map.encode()).equals(Buffer.from(await map.encode({}))), 'GainMap.encode() without arguments');
614614
}
615615

616-
// gradients
616+
// gradients and recolor with an image
617617
{
618+
const {GainMap} = require('..');
618619
const px = (image, x, y) => Array.from(image.bitmap.subarray((y * image.width + x) * 4, (y * image.width + x) * 4 + 4));
619620
const throws = (fn, type, message) => {
620621
let error = null;
@@ -659,6 +660,20 @@ const rgba = (image, x, y) => Image.colorToRGBA(image.getPixelAt(x, y));
659660
'zero radius': () => Image.radialGradient(2, 2, {stops: [1, 2], radius: 0}),
660661
'NaN center': () => Image.radialGradient(2, 2, {stops: [1, 2], x: NaN})
661662
})) throws(call, RangeError, name);
663+
664+
// recolor(image) takes the RGB, keeps the alpha
665+
const shape = new Image(4, 1).fill(x => [0xffffff00, 0xffffff40, 0xffffff80, 0xffffffff][x]);
666+
const filled = shape.clone().recolor(Image.linearGradient(4, 1, {stops: [0xff0000ff, 0x0000ffff], angle: 90}));
667+
const colors = Image.linearGradient(4, 1, {stops: [0xff0000ff, 0x0000ffff], angle: 90});
668+
for (let x = 0; x < 4; x++)
669+
assert(px(filled, x, 0).join() === [...px(colors, x, 0).slice(0, 3), px(shape, x, 0)[3]].join(), `recolor(image), x ${x}`);
670+
assert(px(shape.clone().recolor(new Image(4, 1).fill(0x112233ff)), 3, 0).join() === '17,34,51,255', 'recolor(image) with a flat image');
671+
throws(() => shape.clone().recolor(new Image(3, 1)), RangeError, 'recolor(image) with another size');
672+
assert(shape.clone().recolor(0x336699ff).bitmap[0] === 0x33, 'recolor(color) still works');
673+
const mapped = new Image(8, 8).fill(0xffffffff);
674+
mapped.gainMap = new GainMap(new Image(4, 4).fill(0x808080ff), {gainMapMin: 0, gainMapMax: 2});
675+
mapped.recolor(Image.linearGradient(8, 8, {stops: [0xff0000ff, 0x0000ffff], angle: 90}));
676+
assert(px(mapped, 0, 0)[0] > 200 && px(mapped, 7, 0)[2] > 200 && mapped.gainMap && mapped.gainMap.width === 4, 'recolor(image) keeps the gain map');
662677
}
663678

664679
// Colors: the color helpers, and the deprecated Image ones they replaced, give the same values

0 commit comments

Comments
 (0)