Krita transform tool produces aliased edges

Greetings Krita enthusiasts,

When using the transform tool to rotate a QRCode on a picture (using Ctrl + click) I get aliased edged on the end result, even with bilinear or bicubic. And that makes me a sad panda, especially given I don’t see the same behavior in Photoshop.

Am I missing something ?

Hello @bunjee and welcome to the forum :slight_smile:

I’d be surprised if rotation or perspective shifts didn’t introduce anti-aliased edges from initially horizontal/vertical lines and edges.

Can you post the same image with the transform done in Photoshop?

Thank you @AhabGreybeard

Here is something similar in Photoshop, I get proper antialiased edges on both sides:

What’s odd to me is that Krita seems to get one of the side right while leaving the other aliased.

That image from Photoshop has a canvas more than twice the size of the canvas shown in krita and the QR code occupies a greater proportion of the canvas.
Call it about 4 x the size.

Looking at it, there actually is antialiasing on the edges but to a lesser proportional amount than in krita, as might be expected given its relative larger size.

If you perform the transform on the same image as you did in krita, but using the same sizes in Photoshop, is it any better?

1 Like

Here is another one, same canvas, Photoshop on top, Krita bottom:

Right edges are equivalent, left edge is messed up at the bottom.

Can you provide the original QR code .png file using a file link to a file sharing service/website?

1 Like

Sure, it’s available there: VBML/track/road/dist/tag/swap.png at master · 3unjee/VBML · GitHub

I seem to be able to reproduce this by drawing a square with the rectangle tool and then transforming.

2 Likes

The effect varies depending on the amount of rotation and if using multi-axis rotation:

The aliasing effect is visible on the left edge of the QRCode. You would not get the same with Photoshop.

I suspect the workaround would be to make the transformation with twice the size and then resizing the image to force the filtering algorithm to kick in. But that’s not optimal.

If you slowly adjust the rotation angle in Photoshop, do you ever see comparable alising?

I’ll ping @dkazakov to see if he has any ideas and explanations for this.

Yes I can confirm, the left edge is aliased whatever the algorithm used.
I didn’t tried all possible angle but for the one I’ve tested, the aliasing is here.
And I can reproduce this from Krita 4.4 to 5.3

This is not normal a normal thing, there’s not reason to have the left edge aliased…

But
I found “something”

The QR code is on left border position:


Aliased:

The QR code is not on left border:

Apply the transform mask:

The left border is properly aliased
And then few millisecond later, it’s aliased…
(note: if QR code is on the left border, it’s immediately aliased, like if border of QR code was cropped… I think there’s a bug already opened for that, I’m searching)

Grum999

5 Likes

Didn’t found anything about this case.
But for sure, there’s something wrong.

I’ve created a bug:
https://bugs.kde.org/show_bug.cgi?id=484677

Grum999

8 Likes

Thank you, dynamic communities like this are gold.

2 Likes

Hi

This afternoon I tried to find the origin of this problem.

Here what I found.

In a first time, the transform is applied properly.

In KisTransformMask::decorateRect():

  • the flag m_d->recalculatingStaticImage is false
  • condition params->isAffine() && !m_d->staticCache.isCacheValid(params) is matched
    – here, rendered tranformation is correct (anti-aliased)
    qDebug trace:
    Partial "Transform Mask 1" 
       src->exactBounds() = QRect(25,25 450x450) 
       src->extent() = QRect(-2,-4 512x512) 
       dst->exactBounds() = QRect(12,28 467x454) 
       dst->extent() = QRect(-2,-4 512x512) 
       rc = QRect(0,0 500x500)
    

Then, after ~1second, there’s a calculation made automatically: I’m not able to understand why this is executed and I currently didn’t found which event trigger this delayed recalculation.

But, when this recalculation is made:

  • the flag m_d->recalculatingStaticImage is true and condition is matched
    – here, rendered tranformation is incorrect (top-left border is aliased)
    qDebug trace:
    Recalculate "Transform Mask 1" 
       src->exactBounds() = QRect(25,25 450x450) 
       dst->exactBounds() = QRect(13,28 466x454) 
       rc = QRect(12,28 467x454)
    

The difference between first case and second one is in dst KisPainter: in the second one is truncated by one pixel QRect(13,28 466x454) instead of QRect(12,28 467x454)

Origin of this QRect is in KisPainter::copyAreaOptimizedImpl(), but from here it start to be complex and I’m not sure if this is the origin of problem (but probably, aliasing comes from here…)

At least there’s 2 problems:

  • a delayed calculation that is (here) not needed
  • calculation from this ‘static image’ that is not made properly (if m_d->recalculatingStaticImage is forced to true then image is updated properly and there’s no aliasing, but I’m sure it’s not the right solution :sweat_smile:)

@dkazakov or @tiar I may need help to dig this deeper; also it seems to be sensitive parts too and there’s a risk to break something else (in term of performance or rendering…)
Any tip to continue to try to understand these 2 problems?

Grum999

6 Likes

I’ve found where the delayed update is triggered (a KisSignalCompressor with a delay of 3000ms)

So I’ve tried to deactivate it: it works.
More or less.

There’s another bug, but probably this another bug already exists, it just can be see due to the current aliasing bug…
As you can see, hidding the parent layer, there’s a remaining artifact:


I think as actually the anti-aliased part is truncated, this problem is not visible.

Edit: the bug was introduced by me with some previous tests :sweat_smile: rollback on this change and hiding parent layer works properly…

I’m not sure about the role of this delayed update, I’ll try to continue to understand this.

The weird thing is, after a long time (maybe 10-15minutes), even if I don’t touch anything another event trigger the recalculation and the aliasing is back :sweat_smile:

So deactivating event is probably not the right way (even if I’m really curious about why there’s a recalculation after 3seconds…): I really need to understand why the ‘static image’ update doesn’t work…

Grum999

4 Likes

Maybe ask Dmitry about it?

Michelist

I tried to reproduce this, and only managed it once, I don’t know what’s special about that layer to trigger it.

But, what I’m seeing is backward to what’s described here. When I hide and reshow the transform mask, it first shows aliased, then after a few seconds, antialiased and moved to the left by 1px.

My debug output looks like this (with additional qDebugs added):

KisTransformMask::decorateRect(
  invalidate cache, start update compressor
KisTransformMask::decorateRect(
  invalidate cache, start update compressor
Partial "Transform Mask 1" src->exactBounds() = QRect(0,0 0x0) src->extent() = QRect(369,183 64x128) dst->exactBounds() = QRect(0,0 0x0) dst->extent() = QRect(-15,247 448x320) rc = QRect(0,512 433x28)
Dumping: "dd_12_partial_src.png.png"
Partial "Transform Mask 1" src->exactBounds() = QRect(-25,65 354x295) src->extent() = QRect(-79,55 512x384) dst->exactBounds() = QRect(0,35 284x369) dst->extent() = QRect(-79,-9 512x576) rc = QRect(0,0 433x512)
Dumping: "dd_13_partial_src.png.png"
Dumping: "dd_12_partial_dst.png.png"
Dumping: "dd_13_partial_dst.png.png"
)KisTransformMask::decorateRect
)KisTransformMask::decorateRect
KisTransformMask::slotDelayedStaticUpdate
KisTransformMask::startAsyncRegenerationJob
KisTransformMask::decorateRect(
Recalculate "Transform Mask 1" src->exactBounds() = QRect(-25,65 354x295) dst->exactBounds() = QRect(-29,34 312x370) rc = QRect(-36,32 365x404)
Dumping: "dd_2_recalc_src.png.png"
Dumping: "dd_2_recalc_dst.png.png"
)KisTransformMask::decorateRect

It shows dst->exactBounds off by 1px vertically, but that can be explained by the anti-aliasing adding 1px to the height (the left side is on the top in this case). It doesn’t explain why it’s not aliased, or why it moves over 1px horizontally. So the problem must be elsewhere.

Output images for reference (zoom in to the top-left):
dd_13_partial_dst, dd_2_recalc_dst:

Arf weird.
On my side I reproduce this systematically…

I have the move by 1px too, but on my side it’s ‘antialiased’ then 3seconds after ‘aliased’

From my additional tests:

  • If I disable the static image recalculation that occurs 3seconds after, everything is fine
  • If I let it activated, the processing time (on a 8000x8000 image) of the ‘static image’ is not made through a worker and block krita for ~6seconds (first calculation I can see tiles being processed…) and result is not as expected…

Grum999

You opened a can of worms. :worm:

It also is aliased first and antialiased after a while here. The reason is that there is 2 rendering paths, at least using the accurate option: one is used for the “inexpensive/fast” preview and the other for the high-quality transformation. The fast one always performs bilinear sampling (even if the filter is set to nearest neighbor in the options) and is used for example when moving the handles. Then, after some seconds of inactivity, the high-quality rendering is triggered.

So, I think I fixed the fast case, which showed aliasing. The issue was purely on how the sampling was implemented. There was an issue on how the dst device points were mapped to the src device pixels. It didn’t map the pixel centers, but the corners so some shifting was noticeable. And then there was an issue on how the bilinear filtering was implemented which also could introduce shifting and produced the aliasing, at least some.

But even then, there are discrepancies between the fast rendering and the high-quality one (comparing in bilinear mode since the fast code always renders using bilinear filtering). It seems like there is still some bad coordinate mapping in the high quality code. But that code is extra hard to comprehend tbh :canned_food: :worm: :bug: :worm: :bug:

I made a draft MR, but more like research info. This is an issue that Dmitry would have to solve i’m afraid. More info here: Draft: Fix artifacts on transform mask (free and maybe perspective) (!2107) · Merge requests · Graphics / Krita · GitLab

4 Likes