Better vector shape handling in masks

Issue #57 resolved
Hombre created an issue

It would be really nice to have more complex vector shapes, like using polygons and curves for the Area masks. To do that, I think that ART should use a more generic algorithm to handle even the actual Rectangle/Ellipse shape by decimating that shape in a polyline (like I did with FlatCurveEditor, for example), then add a fillPoly function to fill it properly.

There’s some filling algorithm around, I’ve found one that could probably be used without much effort from Blender :

https://developer.blender.org/diffusion/B/browse/master/source/blender/blenlib/intern/math_geom.c;3b4a8f1cfa7339f3db9ddd4a7974b8cc30d7ff0b$2411

This proposal is about :

  1. using a standard polyFill function to fill the shape + decimating the rectangle shape into a polyline
  2. then adding a more versatile Polygonal shape, that could take any shape/curve

For the curve model, using pure Bézier curves of degree 3 can be cumbersom to use, but that’s still an option (you can see how it works in any vector graphics software, like Inkscape).

My curve model for the polygon handling is shown in art03.png. It is made of successive Bézier curves of degree 2, with addition of line where necessary. For example, straight corner (roundness = 0.0) are straight lines for the corner, and there are straight lines as well at the beginning and at the end of the corner where roundness = 0.5. The advantage of this is that it can be used internally to represent the existing Rectangle shape, w/o being retro-compatible though (not the same shape than the clipped version). Converting Rectangles to Polygons would be easy (but there would be no return)

Please let me know if that makes sense.

Comments (25)

  1. agriggio repo owner

    your proposal sounds good, thanks! for the moment, though, I’d like to add polygons, but also keep the current rectangles, at least for now. the current shapes make it very easy to emulate radial and gradient masks, so before dropping them I'd like to see how the new code works (especially in terms of usability). we can always drop the current mode later if it turns out to be redundant. what do you think?

    regarding the GUI, how would this integrate in the current area mask? maybe you can draw a mock-up?

  2. Hombre reporter

    No problem to look at the rectangle shape later on, if ever.

    I’ve attached a mockup which shows a polygon with a hovered spot. If you click on the spot, a menu will appear to let you adjust the Roundness through an on preview slider.

    You decided to add sliders for the Spot’s location while I don’t think they were necessary. If we have to add sliders on the right panel, users will have to cross the screen at each adjustment, and will rapidly become annoying IMHO.

    [EDIT]

    I forgot to mention that users will ctrl-click on the preview to add a new closed polygon. However if the number of points it less than 3, the shape will still appear in the list but is considered invalid (you can’t have a surface with only 1 or 1 points) and will be skipped by the algorithm. This kind of invalid polygon can be represent in a different color, so the user understand that it’s not usable as is.

    Deleting point can be done by hovering + right clicking on a point. You can add a point inside an existing segment by ctrl-clicking on that segment.

    Ctrl-clicking “outside” of any polygon will add a new polygon shape entry in the list, like for Spot entries.

  3. agriggio repo owner

    well, a picture is worth more than a thousand words 🙂 I like your mock-up (and also your description). I'd say go ahead! 👍

    quick question: are you planning to also have the possibility to move the entire polygon? (not sure yet if this will be that useful though)

  4. Hombre reporter

    @agriggio So how should I proceed? I’ll create a new branch in my fork, but how will I be able to produce a PR in your repo ?

  5. agriggio repo owner

    There is a “Pull requests” entry in BitBucket’s sidebar that should allow you to do that. If not, you just let me know when the branch is done and I can have a look even without going through Bitbucket (I’m probably not going to use the web interface anyway…)

  6. Hombre reporter

    @agriggio So you’re fine with not having sliders for coordinate and Roundness value in the EditorPanel ?

  7. Hombre reporter

    @agriggio Hi, just some status report, I’ve added a screenshot, and it’s not a mockup. I still have to implement point creation (this shape has been added manually to the arp file.

  8. agriggio repo owner

    I don’t mind at all!

    I’ll try testing it tomorrow or during the weekend.

    Just to make sure: do you consider this ready to be merged, or are you looking for preliminary feedback? anything in particular that I need to pay attention to?

  9. Hombre reporter

    @agriggio

    (for dummies documentation here)

    Here is a step-by-step tutorial:

    [B1] = Left mouse button
    [B3] = Right mouse button

    1. Create a new polygon shape by clicking on the "Add Polygon" button. This will enable the on preview editing to let you create the shape.
    2. Ctrl + Click[B1] in the preview to create the first point. At this point, you can already drag it if you want. The shape list will display the number of point in the shape.
    3. Ctrl + Click[B1] to create the second point. Now you see the first line of the cage, represented as a dashed line, but still no closed polygon, because you need at least three points to have a valid, closed shape. An orange line appears, explanation in the next step.
    4. Ctrl + Click[B1] to create the third point. Now you see the polygon with a white solid line, surrounded by a black solid line. This is the real shape that will limit the effect of the tool. The orange line has also moved. This line is the "Insertion line", and by default, connects the last point of the cage to its first point (it's the closing segment). The 2 points at each end of the orange line shows the extremities of the Insertion line.
    5. Move your cursor away from the shape, and Ctrl + Click[B1] to add a point. The new point is taking place inside the Insertion line, i.e. at the end of the points list.
    6. Move over a segment of the polygon : the Insertion line is now under the cursor, which mean that point creation will take place there, between those new previous and next point.
    7. Stay over the segment and Ctrl + Drag[B1] : the new point is created where the insertion line was. Of course, once the point is created, you can release the Ctrl key while still dragging the point. For your convenience, the points and the insertion line are hidden during the adjustment.
    8. Move the cursor over a point. Hints : you may need to move over a segment first to make the extremity points appear. Once your mouse is over a point, this point becomes orange (the orange element is the selected element). The two other points shows now the previous and next points.
    9. You can drag the point to adjust it
    10. While still over the point, use Shift + Drag[B1] to the right. This will let you adjust the roundness of that corner. When dragging to the left, it will go back to a sharp corner. Each point can be rounded. As you can see, if you go all the way right, the curve start at the previous point and end at the next point.
    11. Repeat this step on the previous point, set the roundness to the maximum. Now you can see that, because the previous point is still to it's minimum value, the curve start straight to this previous point. However both the currently edited point and the previously edited point has maximum roundness. In this case, the connection of the curve to the cage segment is done in the middle of the dashed segment. But with different roundness values, it can end up anywhere inside this segment. Changing the roundness value have an impact in the previous and next sub-curve, and it can look awkward at first sight, but there's some tips to make thing easy.
    12. You can move over a segment and Drag[B1] to move it
    13. You can move over a segment and Shift + Drag[B1] to move the entire shape
    14. The polygon shape should still be selected in the Shape list, so click on the + icon on the right. This button will create a shape of similar type to the one actually selected. Make sure that the On preview editing button is still active (it should be)
    15. Now add 4 points in this new shape to create a big square BUT you'll press Shift + Ctrl + Click[B1] to create the points. This will create points with roundness value to the maximum, and you'll end up with something more like a circle than a square. Each roundness value can still be edited.
    16. Move over a point and Ctrl + [B3] to delete a point. ART won't delete the 3 last points. If you want to delete a shape entirely, you'll have to do that through the Shape list (- button).

    Now the tip to create predictable shapes 🙂

    It's very simple : just alternatively create points with sharp corners (roundness = 0, by using the Ctrl modifier key at creation time) and points with totally round corners (roundness = 100, by using the Ctrl + Shift modifier key at creation time). You end up with a series of Bezier curves of degree 3. The sharp points should be located on the border you want to draw, and the round points are used to control the curvature, so it won't usually be on the border.

  10. Hombre reporter

    @agriggio

    Just to make sure: do you consider this ready to be merged, or are you looking for preliminary feedback? anything in particular that I need to pay attention to?

    For me it’s ready to be merged, and fully functional.

    Few things to note :

    1. I had to create a rtengine::CoordD special class, to handle coordinates with double values. I found this solution less invasive over making the Coord class as a template.
    2. Your AreaMask class didn’t freed up the allocated Geometry for rectangles, so there was a memory leak here, as far as I’ve understood your code
    3. I’ve updated rtgui/areamask.* to add Polygon shape support. It might be interesting for maintenance to separate the classes in their own files, but this is up to you. If so, then there’s some work to do in LabMasksPanel as it can’t BE AreaMask anymore.
    4. I didn’t followed your technique for handling Geometry, by using only ID, because some geometry are added only to visibleGeometry, some other only to mouseOverGeometry, and others to both.
    5. I’ll let you rename the variables the way you like. I prefer using camelCase, but I tried to use the same scheme than you as much as possible.
    6. As you’ll see in the documentation, I didn’t used an on preview slider to control the roundness, I think it’s useless in this case, but it can still be done. I’ve started to create a specific object type in rtgui/edit.h ( OPAdjuster ), but never had time or use for it. It has to be finished if we have to use it. It could be useful for rectangles though.
    7. Sliders for rectangle shape editing are hidden when editing a polygon. I don’t plan to add slider in the EditPanel for polygon editing, that would complicate things a lot in the code, and I presume nobody will use it anyway. On preview interaction is quite complete.
    8. I’ve replicated the way that shapes are saved in the arp files, but you might end up with big files for complex editing. I hesitated to save the values like curves are, with a series of numbers. Feel free to change that if you want.
    9. When duplicating a rectangle, you were copying the last selected rectangle shape for the new one. Now it create a default shape, I didn’t find out how to preserve that functionality, and didn’t investigated that much, I must admit.

    On a side note, I’ve used ART quite a lot during testing without looking everywhere yet, but I’m impressed by the work you’ve done, and with some features that I had in mind like selecting a partial profile when processing images, or saving the snapshots with prefix in the key names. Very good job !

  11. agriggio repo owner

    @Hombre your Bitbucket repository seems private. Can you make it public so that I can pull from it? Thanks!

  12. agriggio repo owner

    Hi @Hombre, I took a (quick) look at the code, and I have started playing with it. It’s already quite good! A couple of comments:

    1. I agree with most of your choices, but I’d like to use lists of numbers in the arp files rather than individual entries for all the fields in each knot. So I’ll change that
    2. I think having a default rectangle in the area mask is just confusing now, so I’ll start empty there
    3. for controlling the roundness factor, I think using the mouse scroll wheel (possibly while holding shift) would be quite nice. You can already change the brush size in the brush mask in this way, so the support is already there – shouldn’t be hard to add
    4. finally, I saw you took the polyFill algorithm from a webpage. Would it be possible to include also the copyright/license of the code in the comment right above it?

    I guess that’s it for now. I will keep playing with this for a while, but I expect to merge quickly! 👍

  13. Hombre reporter

    @agriggio Thanks for the review.

    Which point(s) of the list do you want me to solve ?

    For point 4, the blender project only added the following line :

    /* originally by Darel Rex Finley, 2007 */

    There doesn’t seem to be anything else about the license.

  14. Hombre reporter

    One thing about point 3 : the Shift key can be combined with the Ctrl key to slow down the movement and have a finer adjustment, just like in curves. Will it be possible to have 2 speeds with mouse wheel ?

  15. agriggio repo owner

    just let me know what you want to work on, I'll take the rest (sooner or later 🙂

    Regarding the wheel, it should be easy to implement two different logics according to which keys are pressed.

  16. Hombre reporter

    @agriggio I finally committed the changes, it was longer than expected, I had to understand how the EditSubscriber::scroll method were already used in DrawnMaskPanel . So I had to add a bool parameter to let the provider know if the event had to be propagated. It was really necessary.

    Now scroll wheel can be used w/o modifier key to update the roundness as requested, the Ctrl key can be used to slow down the movement. I’ve also added a 0.5s delay before calling the listener. 0.5s is a good value, it will give the user enough time to set the correct roundness while still being reactive.

    I also fixed a but in the tesselation.

    [PS] Shift + drag[B1] is still available to edit the roundness, for those who don’t like wheels 😉, but I must admit the wheel is very handy 👍

  17. Log in to comment