VG_DRAW_IMAGE_STENCIL confusion, urgent.

Oh well. I tried to implement the stencil draw mode, but I have lots of troubles to get anything usefull output. What I get (my implementation), and what I get when I run test codes on the reference implementation differ a lot!

These are some tests, and I was able to break down the problem to a single pixel blend. Here are some colors:

[0.2, 0.4, 0.6, 1.0] -> Dest-Color (some nice blue)
[0.0, 1.0, 0.0, 1.0] -> Image-Color (Pure Green)
[0.0, 0.0, 0.0, 1.0] -> Paint Color (e.g. Black)

Note: All alpha values are 1, so it does not matter if we’re talking about premultiplied colors or not (at least for now).

Now I take the equation from chapter 10.8 (the one that shows the stencil mode in combination with SRC_OVER). I leave out the division by AlphaTemp. it’s 1 in any case, and the equations are already confusing enough.

For the case of a single color channel (in this case green):

Gdst = ((Aimg * Apaint * Gimg * Gpaint) + Adst * Gdst * (1- Aimg * Apaint * Gimg).

I plug in the constants (e.g. anything that does not change per channel, like dest-alpha and Paint Alpha ect:

Gdst = ((1 * 1 * Gimg * Gpaint) + 1 * Gdst * (1- 11 Gimg).

Simplify:

Gdst = ((Gimg * Gpaint) + Gdst * (1- Gimg).

So it will do simple linear interpolation between Paint-color and Dest-color based on the image color channel, at least for this simplified case, where all Alpha values are one.

If I apply this equations to the test colors I’ve listed above I ought to get:

[0, 0.4, 0, 1.0] -> Output-Color (some medium green)

However, the reference implementation gives me this color:

[0.2, 0.13, 0.6, 1.0]. That’s a totally different result. Not even close…

Could anyone please help me and give a detailes answer what the stencil draw mode is supposed to do? For the reference, I also attached the test code I’ve used:


void printcolor (char * name, unsigned char * buffer)
{
  float r = buffer[3] / 255.0f;
  float g = buffer[2] / 255.0f;
  float b = buffer[1] / 255.0f;
  float a = buffer[0] / 255.0f;
  printf ("%s [%f, %f, %f, %f]
", name, r,g,b,a);
}


void stenciltest2 (void)
{
  unsigned char buffer[4]; // to read back colors:

  VGuint testdata[] = { 
    0x00ff00ff // Full alpha Green
  };

  VGfloat clearColor[4] = {0.2,0.4,0.6,1};

  // Create the Image:
  VGImage stencil = vgCreateImage (VG_sRGBA_8888, 1,1, VG_IMAGE_QUALITY_NONANTIALIASED);
  vgImageSubData (stencil, testdata, 1, VG_sRGBA_8888, 0,0,1,1);

  // Create the Paint:
  VGImage paint = vgCreatePaint ();
  vgSetPaint (paint, VG_FILL_PATH);
  vgSetColor (paint, 0x000000ff); // Full alpha black:

  // set a known destination color:
  vgSetfv (VG_CLEAR_COLOR, 4, clearColor);
  vgClear (0,0,10,10);
 
  vgReadPixels  (buffer, 4, VG_sRGBA_8888_PRE, 0, 0, 1, 1);
  printcolor ("Destination = ", buffer);

  vgSeti (VG_IMAGE_MODE, VG_DRAW_IMAGE_STENCIL);
  vgSeti (VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE);
  vgLoadIdentity();
  vgScale (10,10);
  vgSeti (VG_BLEND_MODE, VG_BLEND_SRC_OVER);
  vgDrawImage (stencil);

  vgReadPixels  (buffer, 4, VG_sRGBA_8888_PRE, 0, 0, 1, 1);
  printcolor ("Output  = ", buffer);

  vgDestroyImage (stencil);
  vgDestroyPaint (paint);
}

Judging from just the Red* value, the reference implementation and your equation simplification look correct.

[0.2, 0.4, 0.6, 1.0] -> Dest-Color (some nice blue)
[0.0, 1.0, 0.0, 1.0] -> Image-Color (Pure Green)
[0.0, 0.0, 0.0, 1.0] -> Paint Color (e.g. Black)

Gdst = ((Gimg * Gpaint) + Gdst * (1- Gimg).

Gives a red* of:

Gdst = ((0 * 0) + 0.2*(1 - 0) = 0.2

Check your arithmetic

(* edit I originally said ‘green’ for some reason - I don’t know what I was thinking…)

Hi Ivo,

Yes, I messed up something. Guess I worked, thought and tried to long on the same problem. :stuck_out_tongue:

I made a little test program that just blends the colors and prints them out. Straight forward, without any simplifications.


float testblend (float Aimg, float Apaint, float Adst, float Cimg, float Cpaint, float Cdst)
{
  float Atmp = (Aimg * Apaint + Adst*(1.0f-Aimg*Apaint));
  float  c = (Aimg * Apaint * Cimg * Cpaint) + Adst * Cdst * (1.0f - Aimg * Apaint * Cimg);
  return c /Atmp;
}

void main (void)
{
  float r = testblend (1, 1, 1, 0, 0, 0.2);
  float g = testblend (1, 1, 1, 1, 0, 0.4);
  float b = testblend (1, 1, 1, 0, 0, 0.6);
  printf ("r=%f g=%f b=%f
", r,g,b);
}

That gives, with my test colors:

r = 0.2 g = 0 b = 0.6

(as expected… the image color becomes source alpha per channel, and the blend equation just lerps between paint-color and dest-color)

The reference implementation still gives:

r = 0.2 g = 0.13 b = 0.6

That is is way off, and I have no idea how they derive their 0.13 value…

So, anyone has an idea if the reference implementation is simply broken, or if I still don’t got the equations?

Nils

I’m still thinking:

I took the original equations, assumed all variables are assumed to be in non premultiplied format and restated the equations with premultiplied input and output.

Equations from the spec:

tmp = (Aimg * Apaint + Adst*(1.0f-Aimg*Apaint))
Cout = ((Aimg * Apaint * Cimg * Cpaint) + Adst * Cdst * (1.0f - Aimg * Apaint * Cimg)) / tmp
Aout = tmp

-> with premultiplied output: division is obsolete now:

Cout = ((Aimg * Apaint * Cimg * Cpaint) + Adst * Cdst * (1.0f - Aimg * Apaint * Cimg))

-> with premultiplied paint: Cpaint = Cpaint * Apaint

Cout = ((Aimg * Cimg * Cpaint) + Adst * Cdst * (1.0f - Aimg * Apaint * Cimg))

-> with premultiplied Destination: Cdst = Cdst * Adst

Cout = ((Aimg * Cimg * Cpaint) + Cdst * (1.0f - Aimg * Apaint * Cimg))

-> with premultiplied Image Cimg = Cimg * Aimg

This gives the really nice equations (all inputs an output are now premultiplied):

Cout = Cimg * Cpaint + Cdst * (1.0f - Apaint * Cimg)
Aout = Aimg * Apaint + Adst * (1.0f - Apaint * Aimg)

The equations look almost like the SRC_OVER Porter-Duff blending. Just the AlphaSrc used in the Porter-Duff equations is multiplied with the corresponding image color channel. This even makes sense! The image becomes a per channel alpha source that is just multiplied into the equations.

Is it really that simple?

Noone?

Public specifications are not so clear, as in other cases…anyway i’ll try to write down a simple example. Lets say to have:

  • Drawing surface in premultiplied non-linear color space, cleared with rs = 0.2, gs = 0.2, bs = 0.2, as = 0.6
  • A paint color in unpremultiplied non-linear color space of rp = 0.1, gp = 0.4, bp = 0.3, ap = 0.5
  • An image pixel in unpremultiplied non-linear color space of ri = 0.4, gi = 0.3, bi = 0.1, ai = 0.5
  • A source over blend mode, expressed in SourceOver(SrcColorPremultiplied, SrcAlpha, DstColorPremultiplied, DstAlpha)

Now, if you draw the image in stencil mode, the resulting color will be:

rs’ = SourceOver(rp * ap * ri * ai, ap * ri * ai, rs, as)
gs’ = SourceOver(gp * ap * gi * ai, ap * gi * ai, gs, as)
bs’ = SourceOver(bp * ap * bi * ai, ap * bi * ai, bs, as)
as’ = SourceOver(ai * ap, as)

rs’ = SourceOver(0.1 * 0.5 * 0.4 * 0.5, 0.5 * 0.4 * 0.5, 0.2, 0.6)
gs’ = SourceOver(0.4 * 0.5 * 0.3 * 0.5, 0.5 * 0.3 * 0.5, 0.2, 0.6)
bs’ = SourceOver(0.3 * 0.5 * 0.1 * 0.5, 0.5 * 0.1 * 0.5, 0.2, 0.6)
as’ = SourceOver(0.5 * 0.5, 0.6)

rs’ = SourceOver(0.01, 0.1, 0.2, 0.6)
gs’ = SourceOver(0.03, 0.075, 0.2, 0.6)
bs’ = SourceOver(0.0075, 0.025, 0.2, 0.6)
as’ = SourceOver(0.25, 0.6)

Applying source over equations:

rs’ = 0.01 + 0.2 * (1 - 0.1) = 0.19
gs’ = 0.03 + 0.2 * (1 - 0.075) = 0.215
bs’ = 0.0075 + 0.2 * (1 - 0.025) = 0.2025
as’ = 0.25 + 0.6 * (1 - 0.25) = 0.7

I wish this example could be of help.
Regards,
Matteo - AmanithVG Team

exactly what I was looking for, thanks matteo.