OpenVG Spec 1.0 errata, part II

I’ve seen that reference implementation has a “strange” behavior in several cases, different from the one specified in the official OpenVG 1.0 specification document.
Here are two of them:

  1. Src, SrcIn, DstIn blend modes coupled with alpha masking.
    The example below shows a path filled with a red color (r=1, g=0, b=0, a=1) and alpha masking enabled. First drawing surface has been cleared with a black color (r=0, g=0, b=0, a=1) and is in the non-linear premultiplied format, then path is drawn using a Src blend mode.
    Image used as alpha mask is totally filled with alpha value equal to 102 (0.4 in the range [0; 1]).
    So according to specifications:
    “The mask alpha values are multiplied by the corresponding alpha values of each primitive being drawn in the clipping and masking stage (stage 5) of the rendering pipeline (see Section 2.5). The masking step is equivalent to replacing the source image with the result of the Porter-Duff operation “Src in Mask” (see Section 12.2).”
    So the final color must be:

Dca’ = Sca * alphaMask -> (r = 1 * 1 * 0.4, g = 0 * 1 * 0.4, b = 0 * 1 * 0.4) -> (r = 0.4, g = 0, b = 0)
Da’ = Sa * alphaMask -> (a = 1 * 0.4) -> (a = 0.4)

It seems that reference produces a correct result only for Dca’, and a wrong result for Da’.
A similar behavior can be seen for SrcIn and DstIn blend modes coupled with alpha masking.
Others blend modes seem ok.


vgSeti(VG_RENDERING_QUALITY, VG_RENDERING_QUALITY_BETTER);
vgSeti(VG_IMAGE_QUALITY, VG_IMAGE_QUALITY_BETTER);
vgSeti(VG_SCISSORING, VG_FALSE);
col[0] = 0.0f;
col[1] = 0.0f;
col[2] = 0.0f;
col[3] = 1.0f;
vgSetfv(VG_CLEAR_COLOR, 4, col);
vgClear(0, 0, 256, 256);

// create image and load external data
imgSrc = vgCreateImage(VG_lRGBA_8888, imgAlphaWidth, imgAlphaHeight, VG_IMAGE_QUALITY_NONANTIALIASED);
vgImageSubData(imgSrc, (const void *)&imgAlphaData[imgAlphaWidth * (imgAlphaHeight - 1)], -imgAlphaDataStride, imgAlphaFormat, 0, 0, imgAlphaWidth, imgAlphaHeight);
vgMask(imgSrc, VG_SET_MASK, 0, 0, 256, 256);
vgSeti(VG_MASKING, VG_TRUE);

// create a path
path0 = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
pathSegs[0] = VG_MOVE_TO_ABS;
pathData[0] = -20.0f;
pathData[1] = 20.0f;
pathSegs[1] = VG_CUBIC_TO_ABS;
pathData[2] = -200.0f;
pathData[3] = 170.0f;
pathData[4] = -200.0f;
pathData[5] = -170.0f;
pathData[6] = -20.0f;
pathData[7] = -20.0f;
pathSegs[2] = VG_CUBIC_TO_ABS;
pathData[8] = -170.0f;
pathData[9] = -200.0f;
pathData[10] = 170.0f;
pathData[11] = -200.0f;
pathData[12] = 20.0f;
pathData[13] = -20.0f;
pathSegs[3] = VG_CUBIC_TO_ABS;
pathData[14] = 200.0f;
pathData[15] = -170.0f;
pathData[16] = 200.0f;
pathData[17] = 170.0f;
pathData[18] = 20.0f;
pathData[19] = 20.0f;
pathSegs[4] = VG_CUBIC_TO_ABS;
pathData[20] = 170.0f;
pathData[21] = 200.0f;
pathData[22] = -170.0f;
pathData[23] = 200.0f;
pathData[24] = -20.0f;
pathData[25] = 20.0f;
pathSegs[5] = VG_CLOSE_PATH;
vgAppendPathData(path0, 6, pathSegs, pathData);

// create and set a paint for fill
fillPaint = vgCreatePaint();
col[0] = 1.0f;
col[1] = 0.0f;
col[2] = 0.0f;
col[3] = 1.0f;
vgSetParameterfv(fillPaint, VG_PAINT_COLOR, 4, col);
vgSetParameteri(fillPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
vgSetPaint(fillPaint, VG_FILL_PATH);

vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);		
vgLoadIdentity();
vgTranslate(128.0f, 128.0f);
vgScale(0.8f, 0.8f);
vgDrawPath(path0, VG_FILL_PATH);
eglSwapBuffers(display, surface);

vgDestroyImage(imgSrc);
vgDestroyPath(path0);
vgDestroyPaint(fillPaint);

To see the produced results and the used alpha image, please follow these urls (please, use a webbrowser that supports PNG transparency):
http://www.amanithvg.com/pub_files/repo … nithvg.png
http://www.amanithvg.com/pub_files/repo … erence.png
http://www.amanithvg.com/pub_files/repo … a_mask.png

  1. Color ramp interpolation.
    Interpolating premultiplied color stops is not equal, nor correct, to produce premultiplied colors from the interpolation output.
    The example below shows a linear gradient made of three stops in the format (t, r, g, b, a):
    (0.0, 1.0, 1.0, 1.0, 1.0) - (0.5, 0.0, 1.0, 1.0, 0.0) - (1.0, 1.0, 1.0, 1.0, 1.0).


// initial settings and clear the screen
vgSeti(VG_RENDERING_QUALITY, VG_RENDERING_QUALITY_BETTER);
vgSeti(VG_SCISSORING, VG_FALSE);
vgSeti(VG_MASKING, VG_FALSE);
col[0] = 0.0f;
col[1] = 0.0f;
col[2] = 0.0f;
col[3] = 1.0f;
vgSetfv(VG_CLEAR_COLOR, 4, col);
vgClear(0, 0, 256, 256);

// create a path
path0 = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
pathSegs[0] = VG_MOVE_TO_ABS;
pathData[0] = -20.0f;
pathData[1] = 20.0f;
pathSegs[1] = VG_CUBIC_TO_ABS;
pathData[2] = -200.0f;
pathData[3] = 170.0f;
pathData[4] = -200.0f;
pathData[5] = -170.0f;
pathData[6] = -20.0f;
pathData[7] = -20.0f;
pathSegs[2] = VG_CUBIC_TO_ABS;
pathData[8] = -170.0f;
pathData[9] = -200.0f;
pathData[10] = 170.0f;
pathData[11] = -200.0f;
pathData[12] = 20.0f;
pathData[13] = -20.0f;
pathSegs[3] = VG_CUBIC_TO_ABS;
pathData[14] = 200.0f;
pathData[15] = -170.0f;
pathData[16] = 200.0f;
pathData[17] = 170.0f;
pathData[18] = 20.0f;
pathData[19] = 20.0f;
pathSegs[4] = VG_CUBIC_TO_ABS;
pathData[20] = 170.0f;
pathData[21] = 200.0f;
pathData[22] = -170.0f;
pathData[23] = 200.0f;
pathData[24] = -20.0f;
pathData[25] = 20.0f;
pathSegs[5] = VG_CLOSE_PATH;
vgAppendPathData(path0, 6, pathSegs, pathData);

// create and set a paint for fill
fillPaint = vgCreatePaint();
colStops[0] = 0.0f;
colStops[1] = 1.0f;
colStops[2] = 1.0f;
colStops[3] = 1.0f;
colStops[4] = 1.0f;
colStops[5] = 0.5f;
colStops[6] = 0.0f;
colStops[7] = 1.0f;
colStops[8] = 1.0f;
colStops[9] = 0.0f;
colStops[10] = 1.0f;
colStops[11] = 1.0f;
colStops[12] = 1.0f;
colStops[13] = 1.0f;
colStops[14] = 1.0f;
linGrad[0] = -150.0f;
linGrad[1] = 0.0f;
linGrad[2] = 150.0f;
linGrad[3] = 0.0f;
vgSetParameteri(fillPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_LINEAR_GRADIENT);
vgSetParameteri(fillPaint, VG_PAINT_COLOR_RAMP_SPREAD_MODE, VG_COLOR_RAMP_SPREAD_PAD);
vgSetParameterfv(fillPaint, VG_PAINT_COLOR_RAMP_STOPS, 15, colStops);
vgSetParameterfv(fillPaint, VG_PAINT_LINEAR_GRADIENT, 4, linGrad);
vgSetPaint(fillPaint, VG_FILL_PATH);

vgSeti(VG_BLEND_MODE, VG_BLEND_SRC_OVER);
vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);		
vgLoadIdentity();
vgTranslate(128.0f, 128.0f);
vgScale(0.8f, 0.8f);
vgRotate(45.0f);
vgDrawPath(path0, VG_FILL_PATH);
eglSwapBuffers(display, surface);

vgDestroyPath(path0);
vgDestroyPaint(fillPaint);

To see the produced results, please refer to AmanithVG site:
http://www.amanithvg.com/performance.html

Hi,

I agree with you. I’ve found the same problem for the first part (blending + alpha masking). The reference implementation is inconsistent with the specification on the masking step (see Section 12.2). The way the reference library did is applying masking on the blended result and the original background. The reference implementation is:

r = r * cov + d * (1.0f - cov);

Here r is the color after applying blending, d is the original background color, cov is the alpha masking with antialiasing.

This equation is correct for antialiasing, but not correct for the masking step.

Any discussion on that will be welcome.

Thanks,

Jenny

I haven’t tried the example you provided out to know for sure, but I’m not convinced you guys are right, actually.

The way I read the spec is:
Section 7.2:
“All drawing operations may be modified by an alpha mask, defining an additional alpha value at each pixel of the drawing surface that is multiplied by the coverage value computed by the rasterization stage of the pipeline.”

Meaning it’s the coverage value that is changed, not the color value of the pixel.

The coverage is then used to linearly interpolate (r = r * cov + d * (1.0f - cov)) the final color after blending.
Section 2.8:
“The computed coverage value from stage 5 is used to linearly interpolate between the blended result and the previously assigned color at the pixel to produce an antialiased result.”

I must confess though that the [Sec 7.2] “The mask alpha values are multiplied by the corresponding alpha values of each primitive being drawn in the clipping and masking stage (stage 5) of the rendering pipeline (see Section 2.5). The masking step is equivalent to replacing the source image with the result of the Porter-Duff operation “Src in Mask” (see Section 12.2).” has me a bit confused now though. But please note that paint generation does not happen until stage 6, so any Porter duff blending on any color is undefined - hence it must be speaking about coverage values, not colors.