Add options to wrap-around mode to only do vertical and horizontal
Needs RevisionPublic

Authored by scottpetrovic on Jan 20 2019, 6:45 PM.


Group Reviewers

This adds a couple checkboxes to the main menu by wrap-around mode. You can specify to show the horizontal or vertical axis.

Related wishlist item:

There is one part I need to change...but I cannot figure out where it is in the code. The current patch limits the canvas display to horizontal or vertical, but the actual painting still always wraps both vertical and horizontal. Where is the painting calculation for that being done?

Even with its current limitation, this is still a pretty helpful patch I think.

Test Plan

Turning Wrap-around mode on and off.
Turning the horizontal land vertical modes on and off from the main menu. The canvas display should update accordingly.
Turning both horizontal and vertical off just shows the image...but still wraps both horizontal and vertical (not sure how people would expect this combination to act)

Diff Detail

R37 Krita
Lint Skipped
Unit Tests Skipped
scottpetrovic created this revision.Jan 20 2019, 6:45 PM
Restricted Application added a project: Krita. · View Herald TranscriptJan 20 2019, 6:45 PM
scottpetrovic requested review of this revision.Jan 20 2019, 6:45 PM
yurchor added inline comments.

Typo: vertial -> vertical


Typo: axis -> axes

dkazakov requested changes to this revision.Jan 21 2019, 10:47 AM

Hi, @scottpetrovic!

There is some problem with rendering when using this patch:

0) Create an image 1600x1200

  1. Activate WA mode
  2. Deselect "Vertical WA"
  3. See artifacts:

This revision now requires changes to proceed.Jan 21 2019, 10:47 AM

I updated the logic so it tries to more intelligently calculate the width and height when you toggle the vertical and horizontal. I think this should fix the issue @dkazakov found.

I also updated the minor typos in the comments

dkazakov accepted this revision.Jan 23 2019, 9:12 AM

Hi, @scottpetrovic!

I have tested your patch. It looks like the rendering problem has gone, indeed. Though there are a few other smaller problems :(

  1. BUG: If you enable QPainter canvas, then enable Wraparound (which will be effectively disabled), then switch back into openGL canvas, the WA mode will not be enabled automatically. The checkbox will still be enabled, but WA mode will still be off.
  1. BUG [perhaps the consequence of point 1]: the horizontal and vertical checkboxes can easily go out-of sync:

  1. MAJOR BUG: the state of the checkboxes is not recovered after Krita restart. I see you somehow save it into kritarc, but after restarting Krita the checkboxes are always in all-checked state.
  1. DISPUTABLE: when Horizontal WA Mode is active in the viewport, the strokes are still wrapped in vertical direction. I see that you used "Show..." prefix for the directional options, I guess you wanted to make it more clear that these directional options are only for "showing", not for the WA mode itself. Pehraps we could change the wording a bit more to make it absolutely clear it is for view only?
This revision is now accepted and ready to land.Jan 23 2019, 9:12 AM
dkazakov requested changes to this revision.Jan 23 2019, 9:28 AM

Outch, I didn't intent to accept the patch. It needs fixes :(

This revision now requires changes to proceed.Jan 23 2019, 9:28 AM

@dkazakov I think most of the issues are resolved except the disputable one.

It looks like the wrap-around mode doesn't maintain its state in master either, so I had to add a bit of notification logic to get that to stay in sync better when flipping OpenGL on and off.

the horizontal and vertical modes should also load now when you exit krita and restart.

Hm... I am not sure if it is wise to have this patch if it only changes the wraparound view but not the wraparound strokes. I can understand however that this is in a completely different part of Krita and thus is maybe worth a seperate patch?

Where is the wraparound for strokes defined anyway?

dkazakov requested changes to this revision.Jan 26 2019, 6:47 AM
  1. BUG: Pixel Grid doesn't care about custom WA mode settings. It is painted in both directions whatever the settings are

  1. [Disputable] It would also be nice if all three functions (mage, checkers and grid) would share the 'ifs' code for all the varieties of the WA modes. Right now it looks like code-duplication. Though I'm not sure if it is possible. If you feel it can be resolved, then fix it, if not--- not :)

Basically, pixel grid problem is the only blocker from my side.


Please use setWraparoundModeHorizontalEnabled() naming convention for setters. We don't use ruby-style naming :)

I guess you just followed example of enablePixelGrid(), but that one should also be converted to the standard naming (we seem to have missed that when reviewing that patch).


space is lost :)

This revision now requires changes to proceed.Jan 26 2019, 6:47 AM
dkazakov added subscribers: gdquest, Bollebib.EditedJan 26 2019, 6:58 AM

Hm... I am not sure if it is wise to have this patch if it only changes the wraparound view but not the wraparound strokes.

It might be a kind of compromise. Perhaps wrapping view only would be enough for painters... I would prefer to ask @gdquest and @Bollebib about that.

@gdquest and @Bollebib: How do you think, would it be enough for the painter if we implemented one-directional wrapping for view only? That is the brushes would still wrap over both axes, but the canvas will be tiled in both directions?

Where is the wraparound for strokes defined anyway?

Wraparound mode is defined at the level of KisPaintDevice. kis_paint_device_strategies.h is the file to look at. The full chain looks like that:

  1. KisImage::setWrapAroundModePermitted defines if the user asked for WA
  2. KisStrokeStrategy::supportsWrapAroundMode() defines if the current stroke supports that
  3. If both values are true, then the KisDefaultBounds::wrapAroundMode() also returns true
  4. Default bounds activate a different strategy in KisPaintDevice
  5. Strategies create different kinds of iterators, that wrap over the sides.
  6. There will also be some tricks with updates. See all usages of KisWrappedRect object.

To keep the story short, implementing one-drectional WA mode in the renderer will need quite a lot of small changes in different places :)

scottpetrovic updated this revision to Diff 50988.EditedFeb 5 2019, 8:36 PM

hi @dkazakov !

I think I updated all your points. I ended up refactoring the code so it is shared. Adding more if statements for the pixel grid was a bit sloppy for me, so I moved it all to a new calculation function that does that for the other areas to use later.

The pixel grid I think is working now as well. I also renamed that setter in the config.

I did not add the stroke limiting element to this patch though. Maybe I could do that in another patch if people want that type of control. That also might take me a bit of time as it is in a scary part of the code.

dkazakov requested changes to this revision.Feb 8 2019, 9:38 AM

Hi, @scottpetrovic!

The updated patch fixes the pixel grid problem. Though it breaks checkers rendering :(

I have a feeling that the checkers are just collapsed into a single pixel, thoughI'm not sure.


Oh... this change is a bit dangerous in a long-run. One day someone will try to use the calculated values without calling updateActiveCanvasArea() in advance and will break everything :)

I guess you tried to solved two problems in the same time: return two values from calculation function and (in perspective) optimize this calculation with caching. Such situations are quite common, and there are a few rules of thumb for them:

  1. Do not store calculated temporary values anywhere outside minimally necessary scope. The values should be discarded right after they have been used.
  1. If the values are still need to be stored for caching reasons, ensure either that the values are in always valid state or there is an explicit mark that they are dirty. In the case of activeCanvasArea and canvasWidgetRect, they should be automatically regenerated (or marked as dirty) every time the canvas widget is resized or image is resized/panned/zoomed/rotated. That is quite a lot of (unnecessary) work.

If these rules are not followed, some other developer in the future will try to use these values in expectation that they are "always valid" and will break everything :)

To solve the problem of returning multiple values from a function in C++, you can use either of the two methods:

Pass by pointer (the simplest solution):

void KisOpenGLCanvas2::calculateActiveCanvasArea(QRect *activeCanvasArea, QRect *canvasWidgetRect) const;

Or return a structure (usually it is needed for undo states or something that has multiple instances):

struct CanvasAreaState {
    QRect activeCanvasArea;
    QRect canvasWidgetRect;
CanvasAreaState KisOpenGLCanvas2::calculateActiveCanvasArea() const;

I would prefer the first method, but it is mostly a matter of taste :)

And, yes, without storing the temporaries the calculation method becomes 'const', which is exactly what it is semantically.

This revision now requires changes to proceed.Feb 8 2019, 9:38 AM