personalizedrefrigerator / js-draw

Draw pictures using a pen, touchscreen, or mouse! JS-draw is a freehand drawing library for JavaScript and TypeScript.
https://personalizedrefrigerator.github.io/js-draw/typedoc/
MIT License
84 stars 8 forks source link

Erase only parts an eraser brush? (images) #50

Open jochemstoel opened 11 months ago

jochemstoel commented 11 months ago

How would I go about adding an eraser button that erases only where you move the mouse, like a traditional eraser 'brush'?

Additionally I don't understand yet how js-draw implements events, or how to access the EventDispatcher. For example, how do I set an event listener "on blur" on every layer so that as soon as a new layer is added/unfocused, all layers are merged into one and the editor image is resized to the size of the resulting layer?

personalizedrefrigerator commented 11 months ago

How would I go about adding an eraser button that erases only where you move the mouse, like a traditional eraser 'brush'?

This is definitely something I would like to have bulit-in as a part of js-draw :) .

If implementing this as an outside library, one way to do this would be to

  1. Create a custom tool (see the custom tools example)
  2. Whenever the input device moves, get a list of everything in a region around the pointer
  3. Determine which of these are instanceof Stroke (Stroke docs)
  4. Call getParts on each stroke — determine which actually intersect the eraser's tip (using intersection and similar methods).
    • I just added the getParts method! It isn't released yet, but expect it to be soon (in version 1.10.0)!
  5. For each intersecting segment, get the path (using pathFromRenderable) and either
    • break it into Abstract2DShapes (using Path.geometry) and remove the shapes that intersect the eraser (very rough estimate of how a traditional eraser works, but much easier)
    • break it into Bézier curves/lines (and maybe in the future also elliptical arcs?) using Path.parts and do math to decrease the size/length of some parts and remove others.
  6. Step 5 generated a new set of stroke parts. For each Stroke that would be modified, create new Paths using the new Path constructor. Create new RenderablePathSpecs from these Paths (using pathToRenderable) and create new Strokes with these RenderablePathSpecs (using the new Stroke constructor).
  7. Remove the old strokes and add the new ones (see uniteCommands and Erase).

For how to add/remove tools, see this example.

Additionally I don't understand yet how js-draw implements events, or how to access the EventDispatcher. For example, how do I set an event listener "on blur" on every layer so that as soon as a new layer is added/unfocused, all layers are merged into one and the editor image is resized to the size of the resulting layer?

Use editor.notifier to access the EventDispatcher. A (poorly-commented) example of using it can be found here.

A list of all supported events can be found here.

For example, how do I set an event listener "on blur" on every layer so that as soon as a new layer is added/unfocused, all layers are merged into one and the editor image is resized to the size of the resulting layer?

js-draw currently only has two layers -- a background layer and a non-background layer.

See editor.image.getAllElements and editor.image.getBackgroundComponents can be used to query all non-background components and all background components, respectively. getAllElements, before the addition of the background layer, historically did return all AbstractComponents in the image. Perhaps an equivalent method called something like getForegroundComponents should be added (and getAllElements deprecated).

To auto-resize the image, use editor.image.setAutoresizeEnabled to enable/disable autoresizing the canvas whenever things are added/removed. It's a command, so can be used like this:

// For some editor
editor.dispatch(editor.image.setAutoresizeEnabled(true));
jochemstoel commented 11 months ago

Thank you for your response. If I understand correctly, your described method will enable a user to erase in shapes like lines (essentially by re-rendering them), but not be able to erase parts of an image layer. Is that correct? The main reason to want an eraser is the ability to erase in image layers. Wouldn't this simply be something like a "transparent brush"? on the level of canvas.

My use case for js-draw is to hopefully create an inpaint/outpaint interface. Inpainting means erasing a part of an image and then filling up the empty space (usually with AI). My AI platform already has a backend that does all that. All it needs is a source image and a mask image. (the mask image being the area that needs to be replaced)

I figured all I need is

I really like js-draw for its simplicity and its ability to automatically select only the area with actual content in it, which is exactly what you want in an 'infinite' outpainting canvas. I am not a front-ender by any stretch of the imagination so I'm hoping that I can hack together a prototype without having to design the whole thing from scratch.

Here is an example of such an interface that I'm describing. This particular interface uses a "selection frame" the user can move around like a layer to specifiy which part needs to be inpainted. Also in this particular interface, once a layer is blurred (unselected), it is merged with the rest and can not be moved anymore.

Is it reasonable to want to use js-draw to realize this or should I look for something else?

personalizedrefrigerator commented 11 months ago

This should be easier than creating a stroke eraser, but perhaps still more limited that is ideal. This would be easier if Strokes in js-draw supported clipping. Fabric.js is a library that does support clipping.

One way to do this in js-draw is to

  1. Create an image with a solid-color background (see editor.setBackgroundColor, editor.image.setAutoresizeEnabled)
  2. Create a custom pen (for this case, I do not recommend extending PenTool)
  3. Whenever a stroke is finished with the custom pen, save the image twice:
  4. Replace everything (see uniteCommands, Erase, ImageComponent.fromImage and editor.image.addElement) in the image with the generated image
Wladefant commented 11 months ago

I would use the naming of samsung Notes:

jochemstoel commented 11 months ago

Thanks for your response. I will have to look at it later, right now what you are describing is too complicated since I'm not familiar with all of your APIs.

personalizedrefrigerator commented 6 months ago

Re-opening — the original issue is related to images. Only partial stroke erasing has been implemented.