CircularArrow

Hi Alexey!

Can You send me example drawing CircularArrows?

Dear CAV,

I’m sorry for delay. I was finding way to separate CircularArrows drawing code from
our engine but I’m affraid it’s not possible.

Hi Alexey!

Ok. Can you add more information for Arc and CircularArrow shapes? Or can you send me a formula that describe how to draw Arc and CircularArrow shapes (using only height, width and 2 angles values). As having the data provided by Aspose.PowerPoint I'm unable to draw the mentioned shapes.

Hello CAV.

For a first, you should know, angles are stored in the Adjustments array in degrees multiplied by 65536 (0x10000 in hexadecimal form). The second thing, is that angles used to draw nonscaled (square) shape, so, if you simply write something like

DrawArc(…, shape.Adjustments[ i ] / 65536f, (shape.Adjustments[i+1] - shape.Adjustments[ i ])/ 65536f)

in case shape.Width != shape.Height, you will get wrong result. The simplest way is to draw shape’s elements to GraphicsPath, assuming Width = Height, scale path to shape’s rectangle, and then draw it to target.
With Arc drawing all is more complicated. After drawing arc to GraphicsPath, you should scale to a shape’s size not Arc’s square, but rectangle, which bounds drawn shape (please note: Arc shape has two invisible lines, which connect center of arc’s ellipse with both it’s ends, you can see this if you fill arc, the filled area will have a “pie” form). Unfortunately, you cannot simply call GraphicsPath.GetBounds(): it will return rectangle, based on control points of Bezier curves. You should find it by yourself. Following code should work (I haven’t tested it, but it created from actual code, assuming, you drawn arc in (-1, -1, 2, 2) rectangle), resulting values of left, right, top and bottom define a rectangle, which should be scaled to the shape’s size:

float startAngle = (Adjustments[0] / 0x10000) % 360, endAngle = (Adjustments[1] / 0x10000) % 360;
float startX = Math.Cos(startAngle * Math.PI / 180),
startY = Math.Sin(startAngle * Math.PI / 180),
endX = Math.Cos(endAngle * Math.PI / 180),
endY = Math.Sin(endAngle * Math.PI / 180);
if (startAngle < 0) startAngle += 360;
if (endAngle < 0) endAngle += 360;
float top, left, bottom, right;

// Searching for an arc’s bounds
if (startAngle < endAngle)
{
    top = (startAngle < 270 && endAngle > 270) ? -1 : Math.Min(startY, endY);
    left = (startAngle < 180 && endAngle > 180) ? -1 : Math.Min(startX, endX);
    bottom = (startAngle < 90 && endAngle > 90) ? 1 : Math.Max(startY, endY);
    right = Math.Max(startX, endX);
}
else
{
    top = (startAngle < 270 || endAngle > 270) ? -1 : Math.Min(startY, endY);
    left = (startAngle < 180 || endAngle > 180) ? -1 : Math.Min(startX, endX);
    bottom = (startAngle < 90 || endAngle > 90) ? 1 : Math.Max(startY, endY);
    right = 1;
}

// And the center point
top = Math.Min(top, 0);
left = Math.Min(left, 0);
bottom = Math.Max(bottom, 0);
right = Math.Max(right, 0);

Hi NKuzmin and alcrus!

We have some troubles in conversion of arcs. The code you sent earlier had the wrong output, see 'your.jpg'. We were discovering you code and made some changes to fix the bug. But we didn't fully solve the problem. See the 'our.jpg'.
Also we sent you your source code(your.txt) and your code with our changes(our.txt).
The problem is aggravated when the arc's edges are far from corners of the shape. The sample is in our 'test.ppt' and it's shown at screenshot ('test.jpg').
So we have no idea how to fix it.

See the attached files.rar file.

Following example should work

gp.Reset();

float startAngle = (((AutoShape)sh).Adjustments[0] >> 16) % 360;
float endAngle = (((AutoShape)sh).Adjustments[1] >> 16) % 360;

float startX = (float)/*Math.Round*/(Math.Cos(startAngle * Math.PI / 180)),
startY = (float)/*Math.Round*/(Math.Sin(startAngle * Math.PI / 180)),
endX = (float)/*Math.Round*/(Math.Cos(endAngle * Math.PI / 180)),
endY = (float)/*Math.Round*/(Math.Sin(endAngle * Math.PI / 180));

if (startAngle < 0) startAngle += 360;
if (endAngle < 0) endAngle += 360;

// Searching for an arc's bounds
float top, left, bottom, right;

if (startAngle < endAngle)
{
top = (startAngle < 270 && endAngle > 270)? -1 : Math.Min(startY, endY);
left = (startAngle < 180 && endAngle > 180)? -1 : Math.Min(startX, endX);
bottom = (startAngle < 90 && endAngle > 90)? 1 : Math.Max(startY, endY);
right = Math.Max(startX, endX);
}
else
{
top = (startAngle < 270 || endAngle > 270)? -1 : Math.Min(startY, endY);
left = (startAngle < 180 || endAngle > 180)? -1 : Math.Min(startX, endX);
bottom = (startAngle < 90 || endAngle > 90)? 1 : Math.Max(startY, endY);
right = 1;
}

// And the center point
left = Math.Min(left, 0);
top = Math.Min(top, 0);
right = Math.Max(right, 0);
bottom = Math.Max(bottom, 0);
float width = right - left;
float height = bottom - top;

// target rectangle
float newW = w / width * 2,
newH = h / height * 2,
newX = x - (1 + left) / 2 * newW,
newY = y - (1 + top) / 2 * newH;


gp.AddArc(-1, -1, 2, 2, startAngle, endAngle - startAngle);
System.Drawing.Drawing2D.Matrix matrix = new System.Drawing.Drawing2D.Matrix();
matrix.Scale(newW / 2, newH / 2);
matrix.Translate(newX + newW / 2, newY + newH / 2, System.Drawing.Drawing2D.MatrixOrder.Append);
gp.Transform(matrix);
if (sh.LineFormat.ShowLines) g.DrawPath(pen, gp);

NKuzmin thank you! Yes [Y]

Hi NKuzmin, Alexey!

We spent a long time to calculate a number which pepresents an CirularArrow's edge width. We suppose your library can give this constant without any manual calculation. Also we have to know an arrow's peak value. We guess this information is stored in ppt file and you can read it from ppt and give it to us. (Our code is attached)

Hello CAV!

All your problems are from trying to draw shapes directly to their rectangles.

Change all ints to floats, Points to PointFs, add to the beginning

                float x = 0, y = 0, w = 1, h = 1;

add folowing code to the end of function, before drawing path:

                int rx = 0, ry = 0, rw = 0, rh = 0;
                calculate_size (sh,  ref rx, ref ry, ref rw, ref rh);
                System.Drawing.Drawing2D.Matrix matrix = new System.Drawing.Drawing2D.Matrix();
                matrix.Scale(rw, rh);
                matrix.Translate(rx, ry, System.Drawing.Drawing2D.MatrixOrder.Append);
                gp.Transform(matrix);

in that case, you will be able to calculate X3 and Y3 quite simple

                double DX = (X1 - X2) / 2;
                double DY = (Y1 - Y2) / 2;
                X3 = X2 + DX - DY;
                Y3 = Y2 + DY + DX;

Hi Alexey!

We have some questions about CircularArrows. There is the pr1.ppt file in the attachment.
1. angle1 is equal to 120, but it should be equal to 137.
2. How are calculating the arrow's size (X1,Y1; X2,Y2; X3,Y3)? We tried 2 ways, and the they didn't word properly, you sent us them earlier.

That's they are:
a)
double R = Math.Sqrt( Math.Pow(Ys-Yb,2) + Math.Pow(Xs-Xb,2) ) + 2*ArrowLength;
double K = R/Math.Sqrt(2);
double X = Math.Abs(Xb - Xs);
double Y = Math.Abs(Yb - Ys);
dX = K * Math.Cos(Math.Acos(X/R) - Math.PI/4);
dY = K * Math.Sin(Math.Acos(Y/R) - Math.PI/4);
double X3 = Xs + dX;
double Y3 = Ys - dY;

b)
double DX = (X1 - X2) / 2;
double DY = (Y1 - Y2) / 2;
double X3 = X2 + DX - DY;
double Y3 = Y2 + DY + DX;

----
This code in CircularArrow.cs which is attached too.

http://www.dm.co.kg/ppt/CircularArrow.rar

Hello CAV!

Following code draws CircularArrow exactly same as Aspose.Slides does:

// take parameters
float angle1 = _autoShape.Adjustments[0] / 65536f;
float angle2 = _autoShape.Adjustments[1] / 65526f;
float adj = (float)_autoShape.Adjustments[2] / 21600f;
float circle = 0;
if (angle1>0) circle = 360 - Math.Abs(angle1) + angle2;
if (angle1<0) circle = Math.Abs(angle1 - angle2);
if (angle1>0 && angle2>0) circle = -(angle1 - angle2);

// — new by Max

// take big arc point if (circle < 0) circle += 360;
_gp.AddArc(0, 0, 1, 1, angle1, circle);
float Xb = _gp.PathPoints[_gp.PathPoints.Length-1].X;
float Yb = _gp.PathPoints[_gp.PathPoints.Length-1].Y;
_gp.Reset();

// take small arc point
_gp.AddArc(0.5f - adj, 0.5f - adj, adj2, adj2, angle2, -circle);
float Xs = _gp.PathPoints[0].X;
float Ys = _gp.PathPoints[0].Y;
_gp.Reset();

float sinA = (float)Math.Sin(angle2 / 180 * Math.PI),
cosA = (float)Math.Cos(angle2 / 180 * Math.PI),
sinA90 = (float)Math.Sin((angle2 + 90) / 180 * Math.PI),
cosA90 = (float)Math.Cos((angle2 + 90) / 180 * Math.PI);

float
X1 = 0.5f + cosA * 0.625f,
Y1 = 0.5f + sinA * 0.625f,
X2 = 0.5f + cosA * (adj - 0.125f),
Y2 = 0.5f + sinA * (adj - 0.125f),
DX = (X1 - X2) / 2,
DY = (Y1 - Y2) / 2,
X3 = X2 + DX - DY,
Y3 = Y2 + DY + DX;

// draw
_gp.StartFigure();
_gp.AddArc(0, 0, 1, 1, angle1, circle);
PointF [] arrow = {
new PointF(X1, Y1),
new PointF(X3, Y3),
new PointF(X2, Y2)
};
_gp.AddLines(arrow);
_gp.AddArc(0.5f - adj, 0.5f - adj, adj2, adj2, angle2, -circle);
_gp.CloseFigure();

// thansform points
System.Drawing.Rectangle rect2 = new System.Drawing.Rectangle(_rect.X, _rect.Y, _rect.Width, _rect.Height);
System.Drawing.Drawing2D.Matrix matrix = new System.Drawing.Drawing2D.Matrix();
matrix.Scale(rect2.Width, rect2.Height);
matrix.Translate(rect2.Left, rect2.Top, System.Drawing.Drawing2D.MatrixOrder.Append);
_gp.Transform(matrix);

Thanks a lot.

Just for information. We released new version with AutoShape.Paths property today.
It returns set of GraphicsPath objects so you don’t need to think about how to draw AutoShapes anymore.