Effects and filters
Effects are applied live and stay adjustable. Filters repaint a layer once.
Effects
An effect appears in the effects window. It does not change the layer's pixels, and can be adjusted or taken off at any time. Its function gives the filter for the settings, built with the tools it is handed.
effects: [{
key: 'warm', name: 'Warm evening', group: 'Colors', category: 'Example',
fields: [{ key: 'amount', label: 'Amount', low: 0, high: 1, step: 0.01, usual: 0.6 }],
// Twenty numbers: four rows of red, green, blue, alpha and what is added.
make: ({ amount }, tools) => tools.matrix([
1 + 0.2 * amount, 0, 0, 0, 0.03 * amount,
0, 1 + 0.05 * amount, 0, 0, 0,
0, 0, 1 - 0.25 * amount, 0, 0,
0, 0, 0, 1, 0,
]),
}],
from rassam import slider
@plugin.effect('Warm evening', group='Colors', category='Example', fields=[slider('amount', 'Amount', 0, 1, 0.6, step=0.01)])
def warm(settings, tools):
a = settings.amount
# Twenty numbers: four rows of red, green, blue, alpha and what is added.
return tools.matrix([1 + 0.2 * a, 0, 0, 0, 0.03 * a,
0, 1 + 0.05 * a, 0, 0, 0,
0, 0, 1 - 0.25 * a, 0, 0,
0, 0, 0, 1, 0])
A shader of your own
tools.shader takes a fragment shader. texture(uTexture, vTextureCoord) is the pixel, finalColor what it becomes, and uInputSize the size of what is drawn.
effects: [{
key: 'blinds', name: 'Blinds', category: 'Example',
fields: [{ key: 'size', label: 'Width', low: 2, high: 80, step: 1, usual: 16 }, { key: 'dark', label: 'Darkness', low: 0, high: 1, step: 0.01, usual: 0.5 }],
make: (settings, tools) => tools.shader(`uniform float uSize; uniform float uDark;
void main() {
vec4 c = texture(uTexture, vTextureCoord);
float row = mod(floor(vTextureCoord.y * uInputSize.y / uSize), 2.0);
finalColor = vec4(c.rgb * (1.0 - uDark * row), c.a);
}`, { uSize: settings.size, uDark: settings.dark }),
}],
@plugin.effect('Blinds', category='Example', fields=[slider('size', 'Width', 2, 80, 16), slider('dark', 'Darkness', 0, 1, 0.5, step=0.01)])
def blinds(settings, tools):
return tools.shader('''uniform float uSize; uniform float uDark;
void main() {
vec4 c = texture(uTexture, vTextureCoord);
float row = mod(floor(vTextureCoord.y * uInputSize.y / uSize), 2.0);
finalColor = vec4(c.rgb * (1.0 - uDark * row), c.a);
}''', uSize=settings.size, uDark=settings.dark)
On tools | Makes |
|---|---|
matrix(values) | A change of colors from twenty numbers. |
shader(glsl, numbers) | Anything else, from a fragment shader. The numbers are its uniforms. |
blur(amount) | The program's blur. |
color(hex) | The three parts of '#RRGGBB', each from 0 to 1. |
group is 'Styles', 'Colors' or 'Filters'. category is the heading it is listed under (the plugin's name when left out). An effect's fields are sliders and colors. A list of filters is laid on one after another.
Filters
A filter goes in the Filter menu. It repaints the selected painted layer once, as one step to undo, and its settings are asked for in a window first.
filters: [{
key: 'posterize', name: 'Posterize',
fields: [{ key: 'levels', label: 'Levels', low: 2, high: 16, step: 1, usual: 4 }],
run(pen, settings) {
const picture = pen.getImageData(0, 0, pen.canvas.width, pen.canvas.height), data = picture.data, step = 255 / (settings.levels - 1);
for (let i = 0; i < data.length; i += 4) {
for (let c = 0; c < 3; c++) data[i + c] = Math.round(Math.round(data[i + c] / step) * step); // red, green, blue (the alpha stays)
}
pen.putImageData(picture, 0, 0);
},
}],
@plugin.filter('Posterize', fields=[slider('levels', 'Levels', 2, 16, 4)])
def posterize(picture, settings):
step = 255 / (int(settings.levels) - 1)
table = bytes(round(round(value / step) * step) for value in range(256))
pixels = picture.pixels() # a bytearray: red, green, blue, alpha for each pixel
for channel in range(3): # red, green, blue (the alpha stays)
pixels[channel::4] = pixels[channel::4].translate(table)
picture.put(pixels)
In TypeScript a filter is given the layer's pen, and a loop over every pixel is quick. In Python it is given a picture: picture.pen draws on the layer, picture.pixels() gives its pixels and picture.put(pixels) sets them. Slices are quick in Python where a loop over every pixel is not.