Is it possible to implement a Paint Bucket using Konva.js? - javascript

I’ve managed to implement, using konva, multiple tools that allow users to draw different shapes and patterns like: rectangle, circle, arrows, free draw, eraser, et. c.
I’m trying to achieve something like: using a paint bucket, users should be able to fill different parts of a shape, if over that shape are drawn other shapes or patterns.
Maybe this use case helps to understand better my question:
The user draws a circle.
Afterwards he draws lines over that circle so will be split in multiple areas.
The user uses now the paint bucket and tries to fill only the areas of that circle.
I’m wondering if, using konva, is possible to achieve this functionality.
Until now I've manage only to fill entire shapes, similar to this.
Update
Added images for the use case above.
1 & 2. User draws a circle and lines over it:
Using paint bucket user can fill certain areas of that circle:
Any feedback will be very welcomed.

Bad news: What you want cannot be done with Konvajs, as it is designed to work with vectorial images. Each figure is created as a whole by an equation and is "separated" of other figures (as the lines X and Y and the circle are separate in the Snippet below. It is not a raster layer. To do a paint bucket tool in vector graphics is hard.
(See Good news at the end!)
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var circle = new Konva.Circle({
x: 180,
y: 120,
radius: 50,
fill: 'red',
stroke: 'black',
strokeWidth: 4
});
var lineX = new Konva.Line({
x: 180, // 180-50
y: 120,
points: [-100, 0, 100, 0],
stroke: 'black',
strokeWidth: 4
});
var lineY = new Konva.Line({
x: 180, // 180-50
y: 120,
points: [0, -100, 0, 100],
stroke: 'black',
strokeWidth: 4
});
circle.on('click', function() {
var fill = this.fill() == 'red' ? '#00d00f' : 'red';
this.fill(fill);
layer.draw();
});
layer.add(circle);
layer.add(lineX);
layer.add(lineY);
stage.add(layer);
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://unpkg.com/konva#3.2.6/konva.min.js"></script>
<div id="container"></div>
Good news: But you can do it with Canvas, HTML5 and Javascript.
Here you have a good tutorial which includes a DEMO (on top of the page) and the SOURCE CODE to Create a Paint Bucket Tool in HTML5 and JavaScript
Hope this helps you!

Unless konva has a specific implementation for it that I don’t know of, this is more of an algorithmic problem.
One approach you could take if you decide to implement it on your own is something like a cell automaton. You would create one pixel somewhere in the middle, and it would grow over time (of course you don’t need to show the growth). The rules for it would be that any pixel of the specified color must colorize any pixel around it if it is the same as the average color of pixels around original point (where you clicked to fill color).
Hope this helps :)

I’ve came up with a solution: https://codesandbox.io/s/stupefied-northcutt-1y0cg.
In short, what this solution does is that when the stage is mounted, the paint bucket is setup targeting the canvas generated by konva. The pixels around the one clicked are colored using a cell automaton algorithm, as per Antoni's suggestion.
Okay, but the downside of this approach is that whenever you’re drawing a shape after paint bucket is used, the paint bucket changes get lost because (I assume) render() doesn’t know about the "vanilla" changes made in setupPaintBucket().
Another downside of this approach is that the canvas is blurry.
Sources:
Draw circle, arrow and free hand: https://codesandbox.io/s/43wzzv0l37
Vanilla Paint Program: https://codepen.io/falldowngoboone/pen/zxRXjL

Related

JCanvas: Erasing Canvas doesn't erase draggable elements

I wanted to enhance the drawing application I built with JS by allowing the user to draw various shapes. I want to make it convenient for the users by allowing them to drag these shapes around. So I used jCanvas, since it offers a simple way of dragging a shape:
$("#can").drawRect({
draggable: true,
fillStyle: "#000",
width: 100, height: 100,
x: 100,
y: 100,
});
The problem is, I need a function to erase it again. So I made this script:
<script>
$("#can").drawRect({
draggable: true,
fillStyle: "#000",
width: 100, height: 100,
x: 100,
y: 100,
});
$("#erase").click(function () {
$("#can").clearCanvas();
});
</script>
<canvas id="can" width="200px" height="150px"></canvas>
<button id="erase">Clear Canvas</button>
This doesn't work though. When I erase, it clears out everything at first, but when I try to draw on the Canvas, the shape appears again.
I checked the code of jCanvas, and it appears that it just draws tons of rectangles when dragged, and clears them away to create the illusion of the shape being dragged. But when this happens, the cleared Canvas retreats itself and the shape is visible again.
Is there a way to clear the shape away for eternity? Or do I have to reload the page for that to work?
Thanks in advance
You are not using Layer explicitly, but when you set drag property the Canvas create a layer:
Layers can be made draggable using the draggable property.
https://projects.calebevans.me/jcanvas/docs/draggableLayers/
So, clearCanvas is not suitable:
This method is not meant to be used if you are using the jCanvas Layer API, because the API handles redrawing for you in many cases, and so if you try to clear the canvas. you layers will eventually be redrawn by jCanvas when it deems necessary.
You need remove the layer:
$('canvas').removeLayer(0);
or
$('canvas').removeLayers();
Look more options in Doc: https://projects.calebevans.me/jcanvas/docs/manipulateLayers/
If this help you, check my answer as correct or voteup!

I am using node.js and working on a collaborative tool for students and i want to use fabric js in my node project but it is not working

I am working with nodejs and want to use fabricjs in my project but it is not working with my project and I don't know why.
Here is the code:
var canvas = new fabric.Canvas('canvas');
// create a rectangle object
var rect = new fabric.Rect({
left: 10,
top: 10,
fill: 'red',
width: 100,
height: 100
});
// "add" rectangle onto canvas
canvas.add(rect);
I am also including the lib files of fabricjs, here I am able to draw a rectangle on canvas but the rectangle cannot be resized, moved or rotated.
This is the issue I am facing here. According to fabricjs we can do so by only drawing shapes we do not need to include anything else.

PIXI: Change only non transparent pixels of sprite to one solid color

Here is a solution that just replaces the entire texture with a solid color:
sprite.texture = PIXI.Texture.WHITE
Obviously this wont work with transparent sprites. Any transparency ends up solid as well. (Resulting in a solid rectangle)
How could I change the color of only non transparent pixels of the sprite?
(tint wont work either since all sprites would have to be white)
As far as I know, you can't do it with Pixi. According to one of Pixi maintainers here, you can use dark tint from pixi-heaven package (https://github.com/gameofbombs/pixi-heaven). This exact example is listed in its readme:
Make whole sprite of one color:
sprite.tint = 0xffaacc;
sprite.color.dark[0] = sprite.color.light[0];
sprite.color.dark[1] = sprite.color.light[1];
sprite.color.dark[2] = sprite.color.light[2];
//dont forget to invalidate, after you changed dark DIRECTLY
sprite.color.invalidate();
You could apply a custom filter to the sprite. "Filter" is PixiJS's term for a shader.
Start with this custom filter example.
Modify the file shader.frag with a shader that returns the desired color or transparent, depending on the current pixel.
Optional: The above will continuously calculate the solid sprite from the original sprite. If performance is a problem, I think there is a way to save the results and set that as the sprite's texture.
The fragment shader should be pretty simple. Here are two good fragment shader tutorials:
WebGL Fundamentals (Specifically, this part: WebGL Image Processing)
The Book of Shaders
It is possible in pure PIXI with ColorMatrixFilter:
let filter = new PIXI.filters.ColorMatrixFilter();
filter.matrix = [
10, 10, 10, 10,
10, 10, 10, 10,
10, 10, 10, 10,
-1, -1, -1, 1
];
sprite.filters = [filter];
AFAIK tint and alpha might stop working, but you can add additional filters after this one (e.g. AlphaFilter) to achieve a similar effect.

How to fill custom shape/path using EaselJS

I am exploring CreateJS. This question concerns EaselJS.
I have managed to draw 3 arcs and fill them with a color.
Although, I want the final result to look like this:
If I draw a triangle to fill the missing space, how can I remove the color inside the bottom arc?
If I set it to transparent it will display the triangle color.
I could set it to white to look like what I want in this case, but it would look bad on top of a colored background.
Is there any method in the API that I am missing in order to do something similar to a flood fill inside a shape drawn with multiple arcs/lines?
Instead of drawing three separate arcs, you should use arcTo to keep the path continuous.
Something like this (of course replacing the numbers with your points/control points):
new Graphics().beginStroke(STROKE_COLOR)
.moveTo(0, 20)
.arcTo(150, 20, 150, 70, 50)
.arcTo(150, 20, 150, 70, 50)
.arcTo(150, 20, 150, 70, 50)
.endStroke();

KineticJS strokeWidth of 1 causes a blurred, semi-opaque line instead of a sharp 1 pixel line

I've looked around the internet and found nothing, I've looked on other KineticJS examples that use a strokeWidth of 1 on their rectangles and they all appear to have a semi-opaque 2 pixel line rather than a nice sharp 1px opaque black line.
Now, I am guessing that as Google has nothing that the solution is either really simple or impossible, but.. do you know how I can get a one px border using KineticJS?
$(window).load(function(){
var stage = new Kinetic.Stage({container: "kineticdiv", width: 700, height: 400});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: stage.attrs.width/2, y: stage.attrs.height/2,
width: 100, height: 100,
fill: "#eee", stroke: "black", strokeWidth: 1
});
layer.add(rect);
stage.add(layer);
});
Anyone got any ideas?
when you draw a line from (x,y1) to (x,y2) (say; the same is true for horizontal lines) you need to worry about whether x is "in the middle of a pixel". if the line is "between pixels" then it will be half in one and half in another. the result will look blurred (it's basically anti-aliasing).
graphics systems vary on whether coordinates are for corners or centres, but you can fix the issue by experimenting a little - you just need to add half a pixel width to the coord and try again.
in the case of an html5 canvas (0,0) is the top left corner, so if you have no transform i guess the top left pixel centre is at (0.5, 0.5).
Another approach: if you use Integer numbers as coordinates and ortogonal 1px weight lines, then you can move the whole stage by [0.5, 0.5] and you dont have to add the half of a pixel to each coordinate, you can then use Integer numbers as coordinate as your whole stage will be moved half of pixel to right and the same to down.
There is a cool approach to get exactly what you want: group two similar shapes. The one at the lower level is one pixel larger then the one at the top. Fill the bottom one with the color you want your border (in your case: Black). works fine for me and has the precision and quality of CSS
The easiest way of solving this with Kinetic is to use the offset properties. So, rather than shifting individual coordinates of what you're drawing, your entire line/shape/group/layer/stage is offset by that much, theoretically getting it where you want it with minimum fuss:
var rect = new Kinetic.Rect({
x: stage.attrs.width/2, y: stage.attrs.height/2,
width: 100, height: 100,
fill: "#eee", stroke: "black", strokeWidth: 1,
offsetX: 0.5,
offsetY: 0.5
});
or, to get a whole bunch of stuff at once:
var layer = new Kinetic.Layer({
offsetX: 0.5,
offsetY: 0.5
});
That said, not all items benefit from this trick. Some, in fact, get fuzzier. So, make sure to apply the offset at the most atomic level that avoids contaminating shapes that don't benefit from it.

Categories