Skip to content
  • Øyvind "pippin" Kolås's avatar
    babl: symmetric conversions between associated and separate alpha. · a4d60784
    Øyvind "pippin" Kolås authored
    Provide symmetric, color data preserving conversions between associated and
    separate alpha for alpha values near and equal to 0.0 at single precision
    floating point. Thus these symmetric conversions also augment associated alpha
    to always maintain color information.
    
    This is achieved by clamping the alpha used when computing the color
    components when multiplying/dividing components between separate and
    associated alpha to BABL_ALPHA_FLOOR and -BABL_ALPHA_FLOOR for values
    near 0.0, the alpha value is copied unmodified between pixels; the main
    improvement of this commit.
    
    In the implementation BABL_ALPHA_FLOOR is 1/65536 = 0.00001526.. small
    enough that it vanishing in rounding for 16bit integer alpha but large
    enough to retain color data.
    
    The following table illustrates what corresponding values are between the
    two alpha encodings supported. We here usea BABL_ALPHA_FLOOR of 0.01 instead
    of 0.00001526 to make it easier to see what happens.
    
    ```
    "RGBA float"                     "RaGaBaA float"
    separate alpha                   associated alpha
    non-premultiplied                pre-multiplied alpha
    
        R       G      B      A         Ra     Ga     Ba     A
      10.000   1.000  0.100  0.000     0.100  0.010  0.001  0.000
      10.000   1.000  0.100  0.005     0.100  0.010  0.001  0.005
    __10.000___1.000__0.100__0.010_____0.100__0.010__0.001__0.010__
      10.000   1.000  0.100  0.020     0.200  0.020  0.002  0.020
      10.000   1.000  0.100  0.200     2.000  0.200  0.020  0.200
      10.000   1.000  0.100  0.400     4.000  0.400  0.040  0.400
    
    1000.000 100.000 10.000  0.000    10.000  1.000  0.100  0.000
    1000.000 100.000 10.000  0.005    10.000  1.000  0.100  0.005
    1000.000_100.000_10.000__0.010____10.000__1.000__0.100__0.010___
     500.000  50.000  5.000  0.020    10.000  1.000  0.100  0.020
      50.000   5.000  0.500  0.200    10.000  1.000  0.100  0.200
      25.000   2.500  0.250  0.400    10.000  1.000  0.100  0.400
    ```
    
    GEGL lets each operation compute with it's preferred encoding - for some
    operations like blurs this is associated alpha, for others like color
    adjustments it is separate alpha, perhaps even in CIE Lab based encodings
    rather than RGB. An earlier iteration of this approach was already in use in
    babl making un-erase mode and similar features in GIMP-2.10 work correctly
    after blurring, the previous implementation did not preserve the
    alpha value.
    
    Just the core of the implementation follows:
    
    ```
     #define BABL_ALPHA_FLOOR_F  (1.0f/65536.0f)
    
    static inline float
    babl_epsilon_for_zero_float (float value)
    {
     if (value <= BABL_ALPHA_FLOOR_F)
     {
       /* for performance one could directly retun BABL_ALPHA_FLOOR_F here
          and dropping handling negative values consistently. */
       if (value >= 0.0f)
         return BABL_ALPHA_FLOOR_F;
       else if (value >= -BABL_ALPHA_FLOOR_F)
         return -BABL_ALPHA_FLOOR_F;
     }
     return value;  /* most common case, return input value */
    }
    
    static inline void
    separate_to_associated_rgba (const float *separate_rgba,
                                       float *associated_rgba)
    {
      float alpha = babl_epsilon_for_zero_float (separate_rgba[3]);
    
      associated_rgba[0] = separate_rgba[0] * alpha;
      associated_rgba[1] = separate_rgba[1] * alpha;
      associated_rgba[2] = separate_rgba[2] * alpha;
      associated_rgba[3] = separate_rgba[3]; /* direct copy */
    }
    
    static inline void
    associated_to_separate_rgba (const float *associated_rgba,
                                       float *separate_rgba)
    {
      float alpha = babl_epsilon_for_zero_float (associated_rgba[3]);
      float reciprocal_alpha = 1.0f / alpha; /* the conditional normally in
                                                this conversion to avoid
                                                division by zero is handled by
                                                epsilon_for_zero */
      separate_rgba[0] = associated_rgba[0] * reciprocal_alpha;
      separate_rgba[1] = associated_rgba[1] * reciprocal_alpha;
      separate_rgba[2] = associated_rgba[2] * reciprocal_alpha;
    
      separate_rgba[3] = associated_rgba[3]; /* direct copy */
    }
    ```
    a4d60784