ajstarks/openvg

How to paint a circle

Opened this issue · 10 comments

I draw a circle, use the openvg command.

  1. draw a outside Arc.
  2. draw a line to the inside Arc end point.
  3. draw a inside Arc.
  4. draw a line to the outside Arc start point.
  5. Close the path.
    but I find a strange line between two Arcs. Why does it like this?
    pic_20220405223204

The way this library is set up to draw, each element has its own path. So from what you described, each of the first four steps creates and draws a path which leaves the fifth step to which this library doesn't have. Whilst it uses OpenVG's paths internally, it doesn't expose those paths and doesn't provide any way for you to have multiple elements in a path.

Can you show the code you are using to draw this?

The way this library is set up to draw, each element has its own path. So from what you described, each of the first four steps creates and draws a path which leaves the fifth step to which this library doesn't have. Whilst it uses OpenVG's paths internally, it doesn't expose those paths and doesn't provide any way for you to have multiple elements in a path.

Can you show the code you are using to draw this?

Thanks for your reply. I draw the circle using OpenVG directly. the code is too large, I can show some part.
at first I compute two Arcs using radius, center point, start angle and extent angle
`DrawARC( &outSegments, &outSegments_size, &outCoords, &outCoords_size, 0, 0, out_radius, out_radius, startAngle, -160, &OutCoordsEndX, &OutCoordsEndY);
DrawARC( &inSegments, &inSegments_size, &inCoords, &inCoords_size, 0, 0, in_radius, in_radius, startAngle, -160, &inCoordsEndX, &inCoordsEndY);

// void DrawARC(VGubyte** segments, int* segments_amout, VGfloat** coords, int* coords_amout, VGfloat x, VGfloat y, VGfloat width, VGfloat height, VGfloat startAngle, VGfloat angleExtent, float* end_x, float* end_y)`

and then I compute the circle:
`// outArc Moveto Line inArc Line Close
AllSegments_size = outSegments_size + 1 + 1 + inSegments_size + 1 + 1;
AllCoords_size = outCoords_size + 2 + 2 + inCoords_size + 2 + 0;
AllSegments = (VGubyte*)malloc(sizeof(VGubyte) * AllSegments_size);
AllCoords = (VGfloat*)malloc(sizeof(VGfloat) * AllCoords_size);
AllSegments_index = 0;
AllCoords_index = 0;
for (i = 0; i < outSegments_size; i ++) {
AllSegments[AllSegments_index] = outSegments[i];
AllSegments_index ++;
}
for (j = 0; j < outCoords_size; j ++) {
AllCoords[AllCoords_index] = outCoords[j];
AllCoords_index ++;
}
AllSegments[AllSegments_index] = VG_MOVE_TO_ABS;
AllCoords[AllCoords_index] = outCoords[0];
AllCoords[++ AllCoords_index] = outCoords[1];

AllSegments[++ AllSegments_index] = VG_LINE_TO_ABS;
AllCoords[++ AllCoords_index] = inCoords[0];
AllCoords[++ AllCoords_index] = inCoords[1];

AllSegments_index ++;
AllCoords_index ++;

for (i1 = 0; i1 < inSegments_size; i1 ++) {
	AllSegments[AllSegments_index] = inSegments[i1];
	AllSegments_index ++;
}
for (j1 = 0; j1 < inCoords_size; j1 ++) {
	AllCoords[AllCoords_index] = inCoords[j1];
	AllCoords_index ++;
}

AllSegments[AllSegments_index] = VG_LINE_TO_ABS;
AllCoords[AllCoords_index] = OutCoordsEndX;
AllCoords[++ AllCoords_index] = OutCoordsEndY;

AllSegments[++ AllSegments_index] = VG_CLOSE_PATH;

vgAppendPathData(path, AllSegments_size, AllSegments, AllCoords);`

and then draw it.
in OpenVG, if I need draw a close graphic, shall I draw it continuing? please see the moveto part

I try to draw the outside Arc in clockwise, and then draw the line between then end point of outside Arc and the start point(anti clockwise) of inside Arc, and then draw the inside Arc in anti clockwise, at the end, connect the end point of inside Arc to start point of outside. the strange line is still has.

I think maybe OpenVG can not draw this kind of shape use Arc. It need triangle net to draw it like OpenGL 1.0.

But if a SVG image like this, how OpenVG to paint it ?

I draw a SVG circle in AI.
circle

If I show it in code is same problem.

The VG_CLOSE_PATH will draw a line from the current coordinate which is the end of the last line drawn at (OutCoordsEndX, OutCoordsEndY) back to the very first coordinate of the path which is at (OutCoords[0], OutCoords[1]).

You don't want to use VG_CLOSE_PATH if you've already drawn all the connecting lines and the last coordinate drawn to isn't the same as the first. It's purpose is for closing a loop where the last coordinate drawn to wasn't at the same location as the first but you do want a line from it back to the start.

The VG_CLOSE_PATH will draw a line from the current coordinate which is the end of the last line drawn at (OutCoordsEndX, OutCoordsEndY) back to the very first coordinate of the path which is at (OutCoords[0], OutCoords[1]).

You don't want to use VG_CLOSE_PATH if you've already drawn all the connecting lines and the last coordinate drawn to isn't the same as the first. It's purpose is for closing a loop where the last coordinate drawn to wasn't at the same location as the first but you do want a line from it back to the start.

Thanks again. But I tried to do not use VG_CLOSE_PATH, it still have a line.
jpg_20220406205517
My code:
`// outArc Line Move Line inArc
AllSegments_size = outSegments_size + 1 + 1 + 1 + inSegments_size;
AllCoords_size = outCoords_size + 2 + 2 + 2 + inCoords_size;
AllSegments = (VGubyte*)malloc(sizeof(VGubyte) * AllSegments_size);
AllCoords = (VGfloat*)malloc(sizeof(VGfloat) * AllCoords_size);
AllSegments_index = 0;
AllCoords_index = 0;
for (i = 0; i < outSegments_size; i ++) {
AllSegments[AllSegments_index] = outSegments[i];
AllSegments_index ++;
}
for (j = 0; j < outCoords_size; j ++) {
AllCoords[AllCoords_index] = outCoords[j];
AllCoords_index ++;
}

AllSegments[AllSegments_index] = VG_LINE_TO_ABS;
AllCoords[AllCoords_index] = inCoordsEndX;
AllCoords[++ AllCoords_index] = inCoordsEndY;

AllSegments[++ AllSegments_index] = VG_MOVE_TO_ABS;
AllCoords[++ AllCoords_index] = outCoords[0];
AllCoords[++ AllCoords_index] = outCoords[1];	

AllSegments[++ AllSegments_index] = VG_LINE_TO_ABS;
AllCoords[++ AllCoords_index] = inCoords[0];
AllCoords[++ AllCoords_index] = inCoords[1];

AllSegments_index ++;
AllCoords_index ++;

for (i1 = 0; i1 < inSegments_size; i1 ++) {
	AllSegments[AllSegments_index] = inSegments[i1];
	AllSegments_index ++;
}
for (j1 = 0; j1 < inCoords_size; j1 ++) {
	AllCoords[AllCoords_index] = inCoords[j1];
	AllCoords_index ++;
}

vgAppendPathData(path, AllSegments_size, AllSegments, AllCoords);
vgRemovePathCapabilities(path, VG_PATH_CAPABILITY_APPEND_FROM | VG_PATH_CAPABILITY_APPEND_TO |
                               VG_PATH_CAPABILITY_MODIFY | VG_PATH_CAPABILITY_TRANSFORM_FROM |
                               VG_PATH_CAPABILITY_TRANSFORM_TO | VG_PATH_CAPABILITY_INTERPOLATE_FROM |
                               VG_PATH_CAPABILITY_INTERPOLATE_TO);

vPortFree(inCoords);
vPortFree(inSegments);

vPortFree(outSegments);
vPortFree(outCoords);

vPortFree(AllSegments);
vPortFree(AllCoords);
//vgSeti( VG_STROKE_LINE_WIDTH, 1 );
//vgDrawPath(path, VG_STROKE_PATH | VG_FILL_PATH);
vgDrawPath(path, VG_FILL_PATH);

vgDestroyPath( path );`

When you are filling paths OpenVG uses the areas of their sub-paths to define the area to fill. Each time you have a VG_MOVE_TO segment you are starting a new sub-path so a fill will implicitly close the previous sub-path.

In your example the first sub-path is the outer arc plus the line from the end of the outer arc to the end of the inner arc, that defines the first sub-area.
The next sub-path is just a line between the start of the outer arc and the start of the inner arc, it has no area so doesn't affect the fill.
The last sub-path is just the inner arc, it defines the second sub-area.
The default fill rule is VG_EVEN_ODD so you effectively XOR the sub-areas which is why you see a triangle between the ends of the inner arc along with a triangle cut out of the arcs.

The best way to define the path would be to just concatenate the two arcs as you are (making sure they are in the opposite direction to each other) but change the second arc's initial VG_MOVE_TO segment into a VG_LINE_TO segment (that makes the first connecting line for you) and add a VG_CLOSE_PATH at the very end (which makes the other connecting line). You end up with one complete path that exactly describes the area to fill with no sub-paths to worry about. And it works for stroking as well.

Thanks again. I have tired this way, but the result is same. the outside arc is clockwise, the inside arc is anti-clockwise. and use line to link. but the strange line still is there.
I try another way, use many triangles to split the circle, it do not have the strange line, but the performance is low.

I took time with my previous answer because I wanted to verify my reasoning on a running program. I had to guess at how you created the arc paths, I went with a simple MOVE_TO followed by multiple (20) LINE_TO segments rather than using arc segments (e.g. LCCWARC_TO).
Link to my working example: https://gist.github.com/paeryn/ad473c4f36a692398dbf1e19a68fa1c7

My code similar with you:
`
typedef struct segment_node_{
VGubyte seg;
int index;
float data[5];
int datasize;
struct segment_node_* next;
} segment_node;
void DrawARC(VGubyte** segments, int* segments_amout, VGfloat** coords, int* coords_amout, VGfloat x, VGfloat y, VGfloat width, VGfloat height, VGfloat startAngle, VGfloat angleExtent, float* end_x, float* end_y) {
VGfloat last;
VGfloat angle;
int index = 0;
int segments_index;
int coords_index;
int i;

startAngle = SH_DEG2RAD(startAngle);
angleExtent = SH_DEG2RAD(angleExtent);


segment_node* node;
segment_node* node_last;
segment_node* node_first = (segment_node*)pvPortMalloc(sizeof(segment_node));
last = startAngle + angleExtent;

node_first->index = index;
node_first->seg = VG_MOVE_TO_ABS;
node_first->data[0] = x + cos(startAngle) * width / 2;
node_first->data[1] = y + sin(startAngle) * height / 2;
node_first->datasize = 2;

node = NULL;
if (angleExtent > 0) {
	angle = startAngle + vgvPI;
	while (angle < last) {
		node = (segment_node*)pvPortMalloc(sizeof(segment_node));
		node->seg = VG_SCCWARC_TO_ABS;
		node->data[0] = width / 2;
		node->data[1] = height / 2;
		node->data[2] = 0;
		node->data[3] = x + cos(angle) * width / 2;
		node->data[4] = y + sin(angle) * height / 2;
		node->datasize = 5;
		node->index = index ++;
		angle += vgvPI;			
		if (index == 1) {
			node_first->next = node;
		}
	}
	if (node == NULL) {
		node_first->next = (segment_node*)pvPortMalloc(sizeof(segment_node));
		node = node_first->next;
	}
	else {
		node->next = (segment_node*)pvPortMalloc(sizeof(segment_node));
		node = node->next;
	}
	node->seg = VG_SCCWARC_TO_ABS;
	node->data[0] = width / 2;
	node->data[1] = height / 2;
	node->data[2] = 0;
	node->data[3] = x + cos(last) * width / 2;
	node->data[4] = y + sin(last) * height / 2;
	node->datasize = 5;
	node->index = index ++;
	node->next = NULL;
	end_x[0] = node->data[3];
	end_y[0] = node->data[4];
}
else {
	angle = startAngle - vgvPI;
	while (angle > last) {
		node = (segment_node*)pvPortMalloc(sizeof(segment_node));
		node->seg = VG_SCWARC_TO_ABS;
		node->data[0] = width / 2;
		node->data[1] = height / 2;
		node->data[2] = 0;
		node->data[3] = x + cos(angle) * width / 2;
		node->data[4] = y + sin(angle) * height / 2;
		node->datasize = 5;
		node->index = index ++;
		angle -= vgvPI;			
		if (index == 1) {
			node_first->next = node;
		}
	}
	if (node == NULL) {
		node_first->next = (segment_node*)pvPortMalloc(sizeof(segment_node));
		node = node_first->next;
	}
	else {
		node->next = (segment_node*)pvPortMalloc(sizeof(segment_node));
		node = node->next;
	}

	node->seg = VG_SCWARC_TO_ABS;
	node->data[0] = width / 2;
	node->data[1] = height / 2;
	node->data[2] = 0;
	node->data[3] = x + cos(last) * width / 2;
	node->data[4] = y + sin(last) * height / 2;
	node->datasize = 5;
	node->index = index ++;
	node->next = NULL;
	end_x[0] = node->data[3];
	end_y[0] = node->data[4];
}

node_last = node;
node = node_first;
segments_amout[0] = index + 1;
*segments = (VGubyte*)pvPortMalloc(sizeof(VGubyte) * segments_amout[0]);
coords_amout[0] = 0;

while (node != NULL) {
	coords_amout[0] += node->datasize;
	node = node->next;
}
*coords = (VGfloat*)pvPortMalloc(sizeof(VGfloat) * coords_amout[0]);

node = node_first;
segments_index = 0;	
coords_index = 0;
while (node != NULL) {		
	(*segments)[segments_index] = node->seg;
	i = 0;
	while (i < node->datasize) {
		(*coords)[coords_index] = node->data[i];
		coords_index ++;
		i ++;
	}
	node = node->next;
	segments_index ++;
}
node = node_first;
while (node_first->next != NULL) {
	if (node->next == node_last) {
		vPortFree(node_last);
		node->next = NULL;
		node_last = node;
		node = node_first->next;
	}
}
vPortFree(node_first);

}
`