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
Is there a way to do free drawing of a mask for image filters? #6465
Comments
This is a feature i wanted to build. Ideally you add a maks of effect to filters. To build a mask of effect for filters is the first step i would try to build, it requires to know a bit of the fabricJS internals. |
Thanks for answering. I do know a bit about the fabricJS inner workings, but any help would be highly appreciated. |
Well to work in all occasions we do not need the original picture ( that we have available somewhere during all the process ) but the current image we are going to filter, that in a filter chain can be intermediate step. Now what the mask would do? If the mask is just on/off: if (mask === 0 ) {
return unchangedColor
} else {
return executeFilterLogic();
} if the mask can have different level of opacity: const newColor = executeFilterLogic();
return (newColor * alpha + oldColor * (1 -alpha); Ore something like that... Now adding that filter by filter is really boring, we can probably wrap each filter in an higher order function that can do that, at cost of performances probably. I'm not entirely sure how the code should look like. I make a theoretical example: this is contrast today: WEBGL precision highp float;
uniform sampler2D uTexture;
uniform float uContrast;
varying vec2 vTexCoord;
void main() {
vec4 color = texture2D(uTexture, vTexCoord);
float contrastF = 1.015 * (uContrast + 1.0) / (1.0 * (1.015 - uContrast));
color.rgb = contrastF * (color.rgb - 0.5) + 0.5;
gl_FragColor = color;
} could become precision highp float;
uniform sampler2D uTexture;
uniform sampler2D uMaskTexture;
uniform float uContrast;
varying vec2 vTexCoord;
vec4 interpolate(vec4 filtered, vec4 original, float mask) {
return filtered * mask + original ( 1.0 - mask );
}
void main() {
vec4 color = texture2D(uTexture, vTexCoord);
vec4 mask = texture2D(uMaskTexture, vTexCoord);
float contrastF = 1.015 * (uContrast + 1.0) / (1.0 * (1.015 - uContrast));
color.rgb = contrastF * (color.rgb - 0.5) + 0.5;
gl_FragColor = interpolate(filtered, texture2D(uTexture, vTexCoord), mask.a);
} with the interpolate function being injected in needed and also the final line being added if needed only. Javascript applyTo2d: function(options) {
if (this.contrast === 0) {
return;
}
var imageData = options.imageData, i, len,
data = imageData.data, len = data.length,
contrast = Math.floor(this.contrast * 255),
contrastF = 259 * (contrast + 255) / (255 * (259 - contrast));
for (i = 0; i < len; i += 4) {
data[i] = contrastF * (data[i] - 128) + 128;
data[i + 1] = contrastF * (data[i + 1] - 128) + 128;
data[i + 2] = contrastF * (data[i + 2] - 128) + 128;
}
}, later: applyTo2d: function(options) {
if (this.contrast === 0) {
return;
}
var imageData = options.imageData, i, len,
data = imageData.data, len = data.length,
contrast = Math.floor(this.contrast * 255),
contrastF = 259 * (contrast + 255) / (255 * (259 - contrast)),
destinationImageData = options.destinationImageData.data,
maskData = options.maskData.data,
for (i = 0; i < len; i += 4) {
destinationImageData[i] = contrastF * (data[i] - 128) + 128;
destinationImageData[i + 1] = contrastF * (data[i + 1] - 128) + 128;
destinationImageData[i + 2] = contrastF * (data[i + 2] - 128) + 128;
if (maskData[i+3] !== 1) {
destinationImageData[i] = destinationImageData[i] * maskData[i+3] * data[i] * (1 - maskData[i+3]);
// ... repeat for i + 1 and i + 2 ...
}
}
}, this is consderably slower. |
Then which is the most perforamnt way to load a mask texture and which is also a comfortable way to define it for the developer, i have no idea. I would imagine a monochrome image would be enough, but there are not such things in canvas, you need to instatiate a full image of which you read only the alpha channel. |
I understand, I think that what you described is similar to what I imagined. Regarding the boring part of re-writing the code for each filter, I already used an unorthodox solution/hack for a different purpose (adding watermarks) and I think this could also be used here. Probably JS purist will have my scalp for a solution like this, but in my case it worked :-) The solution I used was to convert some methods to string, do a string replace and then add the method back in.
Do you think something like this would also work? If so, what would be the best way to create the mask texture. To be honest that's my biggest challenge right now, to retrieve image info (width/height) from inside the code of the filter. |
the original image is not always what are you looking for. For the mask it depends what are should cover and how. Gradients? freedrawing? |
sorry if I annoy you, but I just got a different idea that I want to run by you. I'm also thinking of a different solution that could be applied in the applyToWebGL function of each filter. I think that if you apply the filter and get the result, then combine the source with the mask and write the result of that over the previous result you should end up with the filter only applied to the unmasked areas. Since the webgl filters work with webgl canvas (and I don't have much experience with that) I think that this: http://mrdoob.github.io/webgl-blendfunctions/blendfunc.html is the equivalent of the globalcompositeoperation of the 2d context. I think that "source-in" is the globalcompositeoperation for 2d, for webgl I am still trying to figure out |
This is the blending function, isn't it? |
Yes, but I was thinking of doing the masking in the base_filter.applyToWebGL() function, to avoid having to re-write all filters |
You can do that too, if you have source and destination handy. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
@codingdudecom @asturur is there any news about it? |
Actually yes, I've managed to do this using 2 things:
Basically the BlendImage filter takes 2 images and applies an operation to it according to the mode. For the BlendImage filter I added an extra mode to it and called it "mask" which simply multiplies the alpha channels of the image and the mask. This is actually a fragmentSource:
The mask is drawn with a free drawing brush which I extended from the PencilBrush. It basically draws on a black canvas with either Hope this makes sense |
Thank you @codingdudecom. I understand. But I'm not very knowledgeable about webgl. Can you help me with the title here? I can apply alpha mask, but I couldn't make it dynamic with brush. |
I've made a Gist for this: https://gist.github.com/codingdudecom/ba183221d705a23962fcfcd3cae0c63f |
Thx again @codingdudecom, your approach makes sense. I wanted to try your code, but I couldn't. Is my usage correct? |
You're almost there. Made a few adjustments: You need to initialize the brush like this: canvas.freeDrawingBrush = new fabric.MaskBrush( {
canvas:canvas,
target:image,
width:20,
mode: 'source-out',
targetMaskFilter: filter
}); Also, in my example I used Fabric@3.4.0, seems that it also works with the latest version, but there's a little offset in coordinates that you'll have to debug yourself if you need to use the latest version |
@codingdudecom Here is what I want to achieve: |
aha, so somebody did find this useful 😄 I think what you want is to play with the fragment shader. Try this:
You could also make the maskOpacity a uniform that you can later pass to the BlendImage filter to make it adjustable. Anyway, interesting idea you gave me as this might be a good way of allowing the user to see where he's working when masking parts of the image. let me know if this helps |
@codingdudecom
But the problem is that drawn paths overlap each other and makes segment (all drawn parts) look as if they are not related. I can just make opacity 1 but in this case UX is not that good. So I came to this thread 😄 Remaining issueI have no experience on shaders so I am struggling with this task. Can you please also help (guide) me on how to handle following problems:
I would be really grateful if you can help on these problems! |
wow, that's a pretty complex thing you want to achieve and starting with this masking feature might not be the way to go. If I understand correctly you want to draw over the image using several colors for image segmentation (eg. green is foreground, red is background, etc)? My code simply produces an opacity mask. Of course, you might be able to use the same code for drawing over the image and there are ways to pass the color to the shader using uniforms. Basically the passing of uniforms is done via the You'd need to override them to include your extra colors or maybe use different textures (one for each segment/area) to be able to retrieve them later. |
Is there a way to do free drawing of a mask for image filters?
I'm thinking of a situation in which you apply an image filter, but you don't want it to affect the whole image. So, using a brush it would be great if you could add/remove areas of the image that get affected by the filter.
I believe that the BlendImage filter has a "mask" mode that allows using an alphaMask to show/hide parts of the image. For me, it would be sufficient if there was some way in which one would be able to draw or erase on the alphaMask in the BlendImage filter.
Any thoughts about how this could be achieved?
thanks!
The text was updated successfully, but these errors were encountered: