Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ add color curve filter #1600

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Stage } from './Stage';
import { Context } from './Context';
import { Shape } from './Shape';
import { Layer } from './Layer';
import { ColorCurveType } from './filters/ColorCurve';

export type Filter = (this: Node, imageData: ImageData) => void;

Expand Down Expand Up @@ -2576,6 +2577,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
blue: GetSet<number, this>;
brightness: GetSet<number, this>;
contrast: GetSet<number, this>;
colorCurve: GetSet<ColorCurveType, this>;
blurRadius: GetSet<number, this>;
luminance: GetSet<number, this>;
green: GetSet<number, this>;
Expand Down
2 changes: 2 additions & 0 deletions src/_FullInternals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Wedge } from './shapes/Wedge';
import { Blur } from './filters/Blur';
import { Brighten } from './filters/Brighten';
import { Contrast } from './filters/Contrast';
import { ColorCurve } from './filters/ColorCurve';
import { Emboss } from './filters/Emboss';
import { Enhance } from './filters/Enhance';
import { Grayscale } from './filters/Grayscale';
Expand Down Expand Up @@ -69,6 +70,7 @@ export const Konva = Core.Util._assign(Core, {
Blur,
Brighten,
Contrast,
ColorCurve,
Emboss,
Enhance,
Grayscale,
Expand Down
87 changes: 87 additions & 0 deletions src/filters/ColorCurve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Factory } from '../Factory';
import { Node, Filter } from '../Node';

export interface ColorCurveType {
red?: number|number[]
green?: number|number[]
blue?: number|number[]
}

export const RGBA = {
R: 0,
G: 1,
B: 2,
A: 3,
size: 4
}

export const DEFAULT_COLOR_CURVE = {
red: 1,
green: 1,
blue: 1
}

function applyToChannel(imageData: ImageData, channel: number, value?: number|number[]) {
if (!value) return
const data = imageData.data
const length = data.length
if (typeof value === "number") {
if (value < 0) return
for (let i = 0; i < length; i += RGBA.size) {
const idx = i + channel
data[idx] = data[idx] * value
if (data[idx] > 255) data[idx] %= 256
}
} else {
if (value.length !== 256) return
for (let i = 0; i < length; i += RGBA.size) {
const idx = i + channel
data[idx] = value[data[idx] % 256]
}
}

}

/**
* ColorCurve Filter.
* @function
* @memberof Konva.Filters
* @param {Object} imageData
* @author ourfor
* @example
* node.cache();
* node.filters([Konva.Filters.ColorCurve]);
* // number: color -> color * 0.8
* node.colorCurve({red: 0.8});
* // number[]: color -> map[color]
* const blue = []
* for (let i=0; i < 256; i++) blue.push(i <= 128 ? (-1/64) * i * i + 3 * i : (1/64)*i*i - 5*i + 512)
* node.colorCurve({blue})
*/

export const ColorCurve: Filter = function (imageData: ImageData) {
const curve = this.colorCurve()
if (!curve) return
applyToChannel(imageData, RGBA.R, curve.red)
applyToChannel(imageData, RGBA.G, curve.green)
applyToChannel(imageData, RGBA.B, curve.blue)
}

/**
* get/set filter color curve.
* Use with {@link Konva.Filters.ColorCurve} filter.
* @name Konva.Node#colorCurve
* @method
* @param {ColorCurveType} colorCurve an object contains red、green and blue
* @param {Number|Array<Integer>} colorCurve.red a number from 0 to 255 or an array of 256 color values, applied to the red channel
* @param {Number|Array<Integer>} colorCurve.green a number from 0 to 255 or an array of 256 color values, applied to the green channel
* @param {Number|Array<Integer>} colorCurve.blue a number from 0 to 255 or an array of 256 color values, applied to the blue channel
* @returns {ColorCurveType}
*/
Factory.addGetterSetter(
Node,
'colorCurve',
DEFAULT_COLOR_CURVE,
null,
Factory.afterSetFilter
);
2 changes: 2 additions & 0 deletions src/index-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { Blur } from './filters/Blur';
import { Brighten } from './filters/Brighten';
import { Contrast } from './filters/Contrast';
import { ColorCurve } from './filters/ColorCurve'
import { Emboss } from './filters/Emboss';
import { Enhance } from './filters/Enhance';
import { Grayscale } from './filters/Grayscale';
Expand Down Expand Up @@ -159,6 +160,7 @@ declare namespace Konva {
Blur: typeof Blur;
Brighten: typeof Brighten;
Contrast: typeof Contrast;
ColorCurve: typeof ColorCurve;
Emboss: typeof Emboss;
Enhance: typeof Enhance;
Grayscale: typeof Grayscale;
Expand Down
1 change: 1 addition & 0 deletions test/manual-tests.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import './manual/Posterize-test.ts';
import './manual/Sepia-test.ts';
import './manual/Contrast-test.ts';
import './manual/ColorCurve-test.ts';
import './manual/Emboss-test.ts';
import './manual/Solarize-test.ts';
import './manual/Kaleidoscope-test.ts';
Expand Down
100 changes: 100 additions & 0 deletions test/manual/ColorCurve-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { assert } from 'chai';

import { addStage, Konva, loadImage } from '../unit/test-utils';

describe('Filter ColorCurve', function () {
// ======================================================
it('basic', function (done) {
var stage = addStage();

loadImage('darth-vader.jpg', (imageObj) => {
var layer = new Konva.Layer();
var darth = new Konva.Image({
x: 10,
y: 10,
image: imageObj,
draggable: true,
});

layer.add(darth);
stage.add(layer);

darth.cache();
darth.filters([Konva.Filters.ColorCurve]);
darth.colorCurve({red: 0.8});
layer.draw();

assert.equal(darth.colorCurve().red, 0.8);

done();
});
});

// ======================================================
it('tween', function (done) {
var stage = addStage();

loadImage('darth-vader.jpg', (imageObj) => {
var layer = new Konva.Layer();
var darth = new Konva.Image({
x: 10,
y: 10,
image: imageObj,
draggable: true,
});

layer.add(darth);
stage.add(layer);

darth.cache();
darth.filters([Konva.Filters.ColorCurve]);
darth.colorCurve({red: 0.8});
layer.draw();

var tween = new Konva.Tween({
node: darth,
duration: 2.0,
contrast: 0,
easing: Konva.Easings.EaseInOut,
});

darth.on('mouseover', function () {
tween.play();
});

darth.on('mouseout', function () {
tween.reverse();
});

done();
});
});

// ======================================================
it('crop', function (done) {
var stage = addStage();

loadImage('darth-vader.jpg', (imageObj) => {
var layer = new Konva.Layer();
var darth = new Konva.Image({
x: 10,
y: 10,
image: imageObj,
crop: { x: 128, y: 48, width: 256, height: 128 },
draggable: true,
});

layer.add(darth);
stage.add(layer);

darth.cache();
darth.filters([Konva.Filters.ColorCurve]);
darth.colorCurve({red: 0.8});
layer.draw();

assert.equal(darth.colorCurve().red, 0.8);

done();
});
});
});