Skip to content

Fix color dodge and color burn blend modes

Deif Lou requested to merge deiflou/krita:deiflou/fix_color_dodge_mode into master

This MR tries to fix some issues that I found while implementing the new texture modes for brushes and digging into bug 434263.

The issues are:

  1. Incorrect handling of the 0/0 case in the formula. The formulas are basically dst / (1 - src) for color dodge and 1 - (1 - dst) / src for color burn. So something needs to be done for the case where the denominator is 0.
    Currently, part of the problem is solved by checking the src variable and returning the unit value if it would produce a denominator equal to 0. But this is too generic as it doesn't take into account the 0/0 case.
    To fully fix the issue, one has to think of the 0 denominator as an infinitesimal number, so that division by 0 is not possible.
    In the case of a numerator greater than 0, we are dividing a positive number by that infinitesimal number so the result would be an infinite large number. In the current code the unit value is then returned (as if there was some clamping to the unit range), but I'll explain in a following point why this is wrong.
    Bug 434263 happens because the 0/0 case is handled as in the previous paragraph as well. But if the denominator is an infinitesimal number and the numerator is 0, then the result should be 0 and not an infinitelly large number. So we need to check if the numerator is 0 as well.

  2. When handling the special case where the denominator is 0, currently the unit value is returned. But this is not consistent with the generic case, where the clamp function is used. The clamp function clamps to the maximum value allowed by the type in integer spaces and does nothing on floating point spaces. So, as the result of dividing by the infinitesimal number is an infinitelly large number, KoColorSpaceMathsTraits<T>::max should be returned instead of the unit value. Now, as the maximum value is the same as the unit value in integer spaces, the artifacts generated by this issue happen only in floating point spaces and they are not easily spotted. For example, in color dodge if src is equal to 0.99 then the denominator would be 0.01 and the result would be a large number but if src is 1 then the current implementation returns 1 instead of a large number, producing a discontinuity. This is solved by returning the maximum value, as if it were the result of a division by a very small number. Here are some comparison images with the current implementation and the fix. The images are 16 bit floating point:

    unit_value_max_value_dodge
    Color dodge comparison. dst value (the background) is 0.5. From left to right, different values of src. An inconsistent jump is noticeable when the src is 1.0. The black dab at the end is because the result becomes negative when the src is greater than 1.0

    unit_value_max_value_burn
    Color burn comparison. dst value (the background) is 0.5. From left to right, different values of src. The way corner cases are checked in color burn is different from color dodge, but it should be almost identical since the issue is the same: division by 0. In the current implementation a disparity with color dodge can be seen, the dab doesn't get sharper as the src value changes

  3. Infinite/NaN. In the case of floating point spaces the division by a number less than 1 can produce very large numbers very quickly specially when painting over and over in the same area. These computations can result in infinites or NaNs, so some additional check must be done to avoid those results. If the result of the division is infinite or NaN then the maximum value is used instead.

  4. Overflow?, clipping? This last issue (if it is really an issue) is not solved since I think is not really related to this MR although its effects can be seen using the color dodge mode. It seems that in 32 bits mode if the pixels have extremelly large values they show up black instead of white, even if one paints with that large color in normal blending mode. I noticed this because using the color dodge mode can result in very large values very quickly.

Formalities Checklist

  • I confirmed this builds.
  • I confirmed Krita ran and the relevant functions work.
  • I tested the relevant unit tests and can confirm they are not broken. (If not possible, don't hesitate to ask for help!)
  • I made sure my commits build individually and have good descriptions as per KDE guidelines.
  • I made sure my code conforms to the standards set in the HACKING file.
  • I can confirm the code is licensed and attributed appropriately, and that unattributed code is mine, as per KDE Licensing Policy.

Merge request reports