# svg.filter.js

A plugin for [svg.js](https://svgdotjs.github.io) adding filter functionality.

svg.filter.js is licensed under the terms of the MIT License.

- [Examples](#examples)
- [Furthermore](#furthermore)
    - [unfilter](#unfilter)
    - [referencing the filter node](#referencing-the-filter-node)
    - [Animating filter values](#animating-filter-values)
    - [Chaining Effects](#chaining-effects)
- [Effect Classes](#effect-classes)

## Usage

### Npm

```sh
npm i @svgdotjs/svg.filter.js
```

### Yarn

```sh
yarn add @svgdotjs/svg.filter.js
```

Include this plugin after including the svg.js library in your html document.

Here is how each filter effect on the example page is achieved.


## Examples
- [gaussian blur](#gaussian-blur)
- [horizontal blur](#horizontal-blur)
- [desaturate](#desaturate)
- [contrast](#contrast)
- [sepiatone](#sepiatone)
- [hue rotate 180](#hue-rotate-180)
- [luminance to alpha](#luminance-to-alpha)
- [colorize](#colorize)
- [posterize](#posterize)
- [darken](#darken)
- [lighten](#lighten)
- [invert](#invert)
- [gamma correct 1](#gamma-correct-1)
- [gamma correct 2](#gamma-correct-2)
- [drop shadow](#drop-shadow)
- [extrude](#extrude)

### original

```javascript
var image = draw.image('path/to/image.jpg').size(300, 300)
```

### gaussian blur

```javascript
image.filterWith(function(add) {
  add.gaussianBlur(30)
})
```

### horizontal blur

```javascript
image.filterWith(function(add) {
  add.gaussianBlur(30, 0)
})
```

### desaturate

```javascript
image.filterWith(function(add) {
  add.colorMatrix('saturate', 0)
})
```

### contrast

```javascript
image.filterWith(function(add) {
  var amount = 1.5

  add.componentTransfer({
    type: 'linear',
    slope: amount,
    intercept: -(0.3 * amount) + 0.3
  })
})
```

### sepiatone

```javascript
image.filterWith(function(add) {
  add.colorMatrix('matrix', [ .343, .669, .119, 0, 0
                            , .249, .626, .130, 0, 0
                            , .172, .334, .111, 0, 0
                            , .000, .000, .000, 1, 0 ])
})
```

### hue rotate 180

```javascript
image.filterWith(function(add) {
  add.colorMatrix('hueRotate', 180)
})
```

### luminance to alpha

```javascript
image.filterWith(function(add) {
  add.colorMatrix('luminanceToAlpha')
})
```

### colorize

```javascript
image.filterWith(function(add) {
  add.colorMatrix('matrix', [ 1.0, 0,   0,   0,   0
                            , 0,   0.2, 0,   0,   0
                            , 0,   0,   0.2, 0,   0
                            , 0,   0,   0,   1.0, 0 ])
})
```

### posterize

```javascript
image.filterWith(function(add) {
  add.componentTransfer({
    type: 'discrete',
    tableValues: [0, 0.2, 0.4, 0.6, 0.8, 1]
  })
})
```

### darken

```javascript
image.filterWith(function(add) {
  add.componentTransfer({
    type: 'linear',
    slope: 0.2
  })
})
```

### lighten

```javascript
image.filterWith(function(add) {
  add.componentTransfer({
    type: 'linear',
    slope: 1.5,
    intercept: 0.2
  })
})
```

### invert

```javascript
image.filterWith(function(add) {
  add.componentTransfer({
    type: 'table'
    tableValues: [1, 0]
  })
})
```

### gamma correct 1

```javascript
image.filterWith(function(add) {
  add.componentTransfer({
    g: { type: 'gamma', amplitude: 1, exponent: 0.5 }
  })
})
```

### gamma correct 2

```javascript
image.filterWith(function(add) {
  add.componentTransfer({
    g: { type: 'gamma', amplitude: 1, exponent: 0.5, offset: -0.1 }
  })
})
```


### drop shadow
You will notice that all the effect descriptions have a drop shadow. Here is how this drop shadow can be achieved:

```javascript
var text = draw.text('SVG text with drop shadow').fill('#fff')

text.filterWith(function(add) {
  var blur = add.offset(0, 1).in(add.$sourceAlpha).gaussianBlur(1)

  add.blend(add.$source, blur)
})
```

This technique can be achieved on any other shape of course:

```javascript
var rect = draw.rect(100,100).fill('#f09').stroke({ width: 3, color: '#0f9' }).move(10,10)

rect.filterWith(function(add) {
  var blur = add.offset(20, 20).in(add.$sourceAlpha).gaussianBlur(5)

  add.blend(add.$source, blur)

  this.size('200%','200%').move('-50%', '-50%')
})
```

If the drop shadow should get the colour of the shape so it appears like coloured glass:

```javascript
var rect = draw.rect(100,100).fill('#f09').stroke({ width: 3, color: '#0f9' }).move(10,10)

rect.filterWith(function(add) {
  var blur = add.offset(20, 20).gaussianBlur(5)

  add.blend(add.$source, blur)

  this.size('200%','200%').move('-50%', '-50%')
})
```

### extrude
```javascript
image.filterWith(function(add){
  var matrix = add.convolveMatrix([
    1,0,0,0,0,0,
    0,1,0,0,0,0,
    0,0,1,0,0,0,
    0,0,0,1,0,0,
    0,0,0,0,1,0,
    0,0,0,0,0,1
  ]).attr({
    devisor: '2',
    preserveAlpha: 'false'
  }).in(add.$sourceAlpha)

  //recolor it
  var color = add.composite(add.flood('#ff2222'),matrix,'in');

  //merge all of them toggether
  add.merge(color,add.$source);
})
```


## Furthermore
Some more features you should know about.

### unfilter
The `unfilter` method removes the filter attribute from the node:

```javascript
image.unfilter()
```

### creating a reusable filter
its also posible to create a filter by using the `new` keyword
*NOTE: when creating a filter this way, it can take an optional attr object*
```javascript
var filter = new SVG.Filter();

// create the filters effects here
filter.offset(20, 20).gaussianBlur(5);
filter.blend(filter.$source, blur);
filter.size('200%','200%').move('-50%', '-50%')
```
then once you have created the filter you can use it one multiple elements
```javascript
var image = new SVG.Image();
var shape = new SVG.Rect(10, 10);

image.filterWith(filter);
shape.filterWith(filter);
```

### referencing the filter node
An internal reference to the filter node is made in the element:

```javascript
image.filterer()
```

This can also be very useful to reuse an existing filter on various elements:

```javascript
otherimage.filterWith(image.filterer())
```

### Animating filter values
Every filter value can be animated as well:

```javascript
var hueRotate

image.filterWith(function(add) {
  hueRotate = add.colorMatrix('hueRotate', 0)
})

hueRotate.animate(3000).attr('values', 360)
```

### Chaining Effects

[Method chaining](https://en.wikipedia.org/wiki/Method_chaining) is a programing style where each function returns the object it belongs to, for an example look at JQuery.<br>
it's possible to chain the effects on a filter when you are creating them, for example:
```javascript
image.filterWith(function(add){
  add.flood('black',0.5).composite(add.$sourceAlpha,'in').offset(10).merge(add.$source)
})
```

this would create a basic shadow filter where the first input on the `composite` effect would be the `flood` effect, and the input on the offset effect would be the `composite` effect.<br>
same with the `merge` effect, its first input would be the `offset` effect, and its second input would be `add.$source`

some effects like [Merge](#merge), [Blend](blend), [Composite](#composite), [DisplacementMap](displacementmap) have thier arguments changed when they are chained, for example
```javascript
image.filterWith(function(add){
  add.flood('black',0.5).composite(add.$sourceAlpha,'in')
})
```
the `composite` effects first input is set to the `flood` effect and its second input becomes the first argument, this is the same for the merge, blend, composite, and displacmentMap effect. <br>
for more details check out each effects doc below

## Effect Classes

- [Base Effect Class](base-effect-class)
- [Blend](#blend)
- [ColorMatrix](#colormatrix)
- [ComponentTransfer](#componenttransfer)
- [Composite](#composite)
- [ConvolveMatrix](#convolvematrix)
- [DiffuseLighting](#diffuselighting)
- [DisplacementMap](#displacementmap)
- [Flood](#flood)
- [GaussianBlur](#gaussianblur)
- [Image](#image)
- [Merge](#merge)
- [Morphology](#morphology)
- [Offset](#offset)
- [SpecularLighting](#specularlighting)
- [Tile](#tile)
- [Turbulence](#turbulence)

### Base Effect Class

#### in(effect)
  gets or sets the `in` attribute of the effect

  - **effect:** this can be another effect or a string <br>
    if **effect** is not provided it will look for another effect on the same filter whose `result` is equal to this effects `in` attribute, else it will return the value of the `in` attribute
    ```javascript
    image.filterWith(function(add){
      var offset = add.offset(10)

      //create the blur effect and then set its input
      var blur = add.gaussianBlur(3)

      //set the input to an effect
      blur.in(offset)

      //this will return the offset effect
      var input = blur.in()

      //set the input to a string
      blur.in('another-result-as-a-string')

      //this will return a string since there is no other effect which has a matching result attribute
      var input2 = blur.in()
    })
    ```

#### in2(effect)
  gets or sets the `in2` attribute of the effect <br>
  this function works the same as the [in](#ineffect) method. <br>
  it's only on effects ([Blend](#blend), [Composite](#composite), and [DisplacementMap](#displacementmap))

#### result(string)
  gets or sets the `result` attribute of the effect

  - **string:** if a string is provided it will set the value of the `result` attribute. <br>
    if no arguments are provided it will act as a getter and return the value of the `result` attribute

### Blend

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feBlendElement)

```javascript
filter.blend(in1, in2, mode)
//or
new SVG.BlendEffect({in1, in2, mode})
```

- **in1**: an effect or the result of effect
- **in2**: same as **in1**
- **mode**: "normal | multiply | screen | darken | lighten" defaults to "normal"

**chaining** when this effect is called right after another effect, for example:
```javascript
filter.offset(10).blend(filter.$source)
```
the first input is set to the `offset` effect and the second input is set to `filter.$source` or what ever was passed as the first argument, and the second input becomes the **mode**

### ColorMatrix

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement)

```javascript
filter.colorMatrix(type, values);
//or
new SVG.ColorMatrixEffect({type, values});
```

- **type**: "matrix | saturate | hueRotate | luminanceToAlpha"
- **values**
  - **type="matrix"**: values would be a matrix the size of 4x5
  - **type="saturate"**: number (0 to 1)
  - **type="hueRotate"**: number (0 to 360) deg
  - **type="luminanceToAlpha"**: value not needed

### ComponentTransfer

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feComponentTransferElement)

```javascript
filter.componentTransfer(components);
// or
filter.componentTransfer(function (add) { add.funcA({ type... }) });
//or
new SVG.ComponentTransferEffect();
```

- **components**: an object which is set for all chanels or `r`, `g`, `b`, `a` properties for each chanel
  ```javascript
    type: "identity | table | discrete | linear | gamma",

    //type="table"
    tableValues: "0 0.5 2 1", //number separated by spaces

    //type="linear"
    slope: 1, //number
    intercept: 3,//number

    //type="gamma"
    amplitude: 0, //number
    exponent: 0, //number
    offset: 0 //number
  }
  ```

### Composite

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feCompositeElement)

```javascript
filter.composite(in1, in2, operator);
//or
new SVG.CompositeEffect({in1, in2, operator});
```

- **in1**: an effect or the result of an effect
- **in2**: same as **in1**
- **operator**: "over | in | out | atop | xor | arithmetic" defaults to "over"

**chaining** when this effect is called right after another effect, for example:
```javascript
filter.flood('black',0.5).composite(filter.$sourceAlpha,'in')
```
the first input is set to the `flood` effect and the second input is set to `filter.$sourceAlpha` or what ever was passed as the first argument.<br>
also the second argument becomes the **operator**

### ConvolveMatrix

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feConvolveMatrixElement)

```javascript
filter.convolveMatrix(matrix);
//or
new SVG.ConvolveMatrixEffect({matrix});
```

- **matrix**: a square matrix of numbers that will be applied to the image
  - exmaple:
  ```javascript
  [
    1,0,0,
    0,1,0,
    0,0,1
  ]
  ```

### DiffuseLighting

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feDiffuseLightingElement)

```javascript
filter.diffuseLighting(surfaceScale, lightingColor, diffuseConstant, kernelUnitLength);
//or
new SVG.DiffuseLightingEffect({surfaceScale, lightingColor, diffuseConstant, kernelUnitLength});
```

***very complicated, just check out the W3 doc***

### DisplacementMap

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feDisplacementMapElement)

```javascript
filter.displacementMap(in1, in2, scale, xChannelSelector, yChannelSelector);
//or
new SVG.DisplacementMapEffect({in1, in2, scale, xChannelSelector, yChannelSelector});
```

***very complicated, just check out the W3 doc***

**chaining** when this effect is called right after another effect, for example:
```javascript
filter.offset(20,50).displacementMap(filter.$source,2)
```
the first input is set to the `offset` effect and the second input is set to `filter.$source` or what ever was passed as the first argument.<br>
also the second argument becomes the **scale**, and the third argument is the **xChannelSelector** and so on

### Flood

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feFloodElement)

```javascript
filter.flood(color,opacity);
//or
new SVG.FloodEffect(color,opacity);
```

- **color**: a named or hex color in string format
- **opacity**: number form 0 to 1

### GaussianBlur

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement)

```javascript
filter.gaussianBlur(x, y);
//or
new SVG.GaussianBlurEffect({x, y});
```

- **x**: blur on the X
- **y**: blur on the y, will default to the **x** if not provided

### Image

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feImageElement)

```javascript
filter.image(src);
//or
new SVG.ImageEffect({src});
```

### Merge

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feMergeElement)

```javascript
filter.merge();
//or
new SVG.MergeEffect();
```

- **Array**: an Array of effects or effect results `filter.merge([effectOne,"result-two",another_effect])`
- **chaining** you can also chain the merge effect `filter.offset(10).merge(anotherEffect)` which will result in a merge effect with its first input set to the `offset` effect and its second input set to `anotherEffect`

### Morphology

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feMorphologyElement)

```javascript
filter.morphology(operator, radius);
//or
new SVG.MorphologyEffect({operator, radius});
```

- **operator**: "erode | dilate"
- **radius**: a single number or a string of two numbers separated by a space
  - the first number is the X
  - the second number is the Y, if no second number was provided it will default to the first number

### Offset

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feOffsetElement)

```javascript
filter.offset(x, y);
//or
new SVG.OffsetEffect({x, y});
```

- **x**: move on the X
- **y**: move on the y, will default to the **x** if not provided

### SpecularLighting

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feSpecularLightingElement)

```javascript
filter.specularLighting(surfaceScale, lightingColor, diffuseConstant, specularExponent, kernelUnitLength);
//or
new SVG.SpecularLightingEffect(surfaceScale, lightingColor, diffuseConstant, specularExponent, kernelUnitLength);
```

***very complicated, just check out the W3 doc***

### Tile

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feTileElement)

```javascript
filter.tile();
//or
new SVG.TileEffect();
```

***no arguments, but if you want to find out what it does check out the W3 doc***

### Turbulence

[W3 doc](https://www.w3.org/TR/SVG/filters.html#feTurbulenceElement)

```javascript
filter.turbulence(baseFrequency, numOctaves, seed, stitchTiles, type);
//or
new SVG.TurbulenceEffect({baseFrequency, numOctaves, seed, stitchTiles, type});
```

***very complicated, just check out the W3 doc***
