Disable multithreading in threshold filter
AbandonedPublic

Authored by nicholasl on Aug 22 2016, 10:45 AM.

Details

Reviewers
None
Group Reviewers
Krita: Stable bug fixes
Summary

The threshold filter produces inconsistent and incorrect results for me when threading is enabled and the filter is used with an image having a color depth greater than 8 bits.

Typically, the issue shows up as pixels which don't correspond to the source image (e.g. a black pixel from a light pixel in the source, even at a low threshold), or rectangular blocks that are either solid black or have a color which is not black or white. The result will be different each time the filter runs.

It can be reproduced with the following steps:

  • Create or open an image with a color depth greater than 8 bits
  • Use threshold filter
  • Toggle preview (toggling multiple times may be needed)

Diff Detail

Repository
R37 Krita
Lint
Lint Skipped
Unit
Unit Tests Skipped
nicholasl updated this revision to Diff 6135.Aug 22 2016, 10:45 AM
nicholasl retitled this revision from to Disable multithreading in threshold filter.
nicholasl updated this object.
nicholasl edited the test plan for this revision. (Show Details)
nicholasl set the repository for this revision to R37 Krita.
rempt added a subscriber: rempt.EditedAug 22 2016, 10:59 AM

Hm, that shouldn't matter -- the threshold filter is a filter where the neighbouring pixels don't matter. Disabling threading only is needed for those filters that need access to the whole image. There must be something else going on, probably some overflow.

I can reproduce the issue, though.

The logical candidate is the intensity8 function, though that doesn't explain why sometimes a block remains colored.

This is interesting: the RGB u8 colorspace does:

quint8 RgbU8ColorSpace::intensity8(const quint8 *src) const
{

const KoBgrU8Traits::Pixel *p = reinterpret_cast<const KoBgrU8Traits::Pixel *>(src);
return (quint8)(p->red * 0.30 + p->green * 0.59 + p->blue * 0.11);

}

While the base class converts to QColor, uses the same formula _but_ adds 0.5

virtual quint8 intensity8(const quint8 * src) const {
    QColor c;
    const_cast<KoColorSpaceAbstract<_CSTrait> *>(this)->toQColor(src, &c);
    return static_cast<quint8>((c.red() * 0.30 + c.green() * 0.59 + c.blue() * 0.11) + 0.5);
}

Not sure about the addition of 0.5, but your mention of intensity8 made me try something else. I wonder if this is the issue?
(I'm compiling with "CMAKE_BUILD_TYPE=Debug" to prevent unused variables from being optimized out.)

If the call to intensity8 is removed, the colored blocks will go away.
For example, the following gives a fine result, although obviously not a threshold:

if ((rand() % 255) > threshold) {

However, if I do this instead, the blocks are present:

quint8 intensity = device->colorSpace()->intensity8(it.oldRawData());
if ((rand() % 255) > threshold) {

I followed the calls that I expected to be involved with the base class version of intensity8 (toQColor > normalisedChannelsValue > nativeArray, although I may have looked in the wrong files), but nothing immediately stuck out to me as something that would violate const.

rempt added a comment.Aug 22 2016, 4:13 PM

I actually found the real culprit: the base intensity8 function was not threadsafe! I fixed that in 746c6b1558e2f02fa2a9eba27abe437330f4d16c -- and many, many thanks for bringing the problem to our attention. This function has been fundamentally unsafe since 2006!

I'm glad to hear that a problem was fixed! Unfortunately, I pulled the changes and I'm still able to reproduce both the rectangles and the incorrect single pixels. Did your commits fix it for you?

As a note, just after my previous post, I tested memcmp in an assert to compare oldRawData before and after intensity8. There was no change.

rempt added a comment.Aug 22 2016, 5:30 PM

Yes, with that commit I cannot reproduce anymore, before, it was really easy.

Ah, I see. In toQColor, you added the lock inside the "else" clause of an if statement. If I set a breakpoint, I end up inside the "profile == 0" part.
I can no longer reproduce the issue after moving the lock to the first line of the function.

rempt added a comment.Aug 23 2016, 7:22 AM

Ah, I still cannot reproduce a problem, but it might be safer to move the mutexlocker up, so I'll do that.

nicholasl abandoned this revision.EditedAug 23 2016, 8:56 AM

That looks good! I'll close the revision. [Edit : I thought "Abandon Revision" would do that, but it seems not. Is there any preferred procedure for when a patch is accepted or no longer needed?]

Out of curiosity, what should make it logically impossible? Given that moving the lock up fixes it for me, I assumed that qcolordata was modified in another thread before this runs:

c->setRgb(d->qcolordata[2], d->qcolordata[1], d->qcolordata[0]);

I'm slowly learning C++ and this was the first time I looked at a problem related to concurrency, so it's interesting for me to see how it was solved.

rempt added a comment.Aug 24 2016, 6:54 AM

I actually was wrong, I hadn't seen that the d->qcolordata was also used in the other part of the if statement.