middlePoint in CGContextAddArc? - iphone

i am drawing Arc through CGCOntext.I want to draw a string in the center Point of Arc.how can i fond the center point in the Arc which has been drawn through CGContext.
CGContextSetAlpha(ctx, 0.5);
CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha );
CGContextMoveToPoint(ctx, cX, cY);
CGContextAddArc(ctx, cX, cY, radious+10, (startDeg-90)*M_PI/180.0, (endDeg-90)*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextFillPath(ctx);

The 2nd and 3rd arguments to CGContextAddArc are the x and y coordinates for the center of the arc. Therefore, in this code, the center is at the point (cX,cY).
Edit
This code will give the coordinates for the point directly between the starting and ending points of the arc as x and y.
CGFloat x = cX + (radious+10) * (cos((startDeg-90)*M_PI/180.0) + cos((endDeg-90)*M_PI/180.0)) / 2;
CGFloat y = cY + (radious+10) * (sin((startDeg-90)*M_PI/180.0) + sin((endDeg-90)*M_PI/180.0)) / 2;

Related

Draw a circle smoothly transforming to a line

I faced the problem in my Flutter app that I can't draw this kind of a shape for my Slider
What I have now is:
final paint = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
final rect = Rect.fromCircle(center: center, radius: thumbRadius);
final rrect = RRect.fromRectAndRadius(
Rect.fromPoints(
Offset(rect.left - 5, rect.top),
Offset(rect.right + 5, rect.bottom),
),
Radius.circular(thumbRadius + 2),
);
canvas.drawRRect(rrect, paint);
Also, it would be great to change height of all bar, because next code changes only the size after player
SliderTheme(
data: SliderThemeData(
trackHeight: 2,
thumbShape: CustomSliderPlayer(),
),
child: Slider(...)
From the comments it looks like you are not familiar with quadratic bezier curves, they are very simple, I would recommend you to start on a Javascript canvas, they are easier to test that way and logic is the same, we move to the starting point then we draw the curve, see sample snippet below
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function dobleQuad(x, y, w, h) {
ctx.beginPath();
ctx.moveTo(x - w, y);
ctx.quadraticCurveTo(x, y - h, x + w, y);
ctx.moveTo(x - w, y);
ctx.quadraticCurveTo(x, y + h, x + w, y);
ctx.fill();
}
function drawSlider(x, y) {
ctx.moveTo(0, y - 2);
ctx.fillRect(0, y - 2, canvas.width, 4);
dobleQuad(x, y, 20, 22)
}
drawSlider(50, 50)
canvas.addEventListener('mousemove', function(evt) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
var rect = canvas.getBoundingClientRect();
drawSlider( evt.clientX - rect.left, 50 )
})
<canvas id="canvas"></canvas>
Just keep in mind that in JS it's quadraticCurveTo but in flutter quadraticBezierTo
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/quadraticCurveTo
void ctx.quadraticCurveTo(cpx, cpy, x, y);
cpx, cpy
The coordinates of the control point.
x, y
The coordinates of the end point.
https://api.flutter.dev/flutter/dart-ui/Path/quadraticBezierTo.html
void quadraticBezierTo(
double x1, double y1,
double x2, double y2
)
Adds a quadratic bezier segment that curves from the current point
to the given point (x2,y2), using the control point (x1,y1).

I have a line from Point A to B and I want to extend the line to Point C upto outer radius in the same angle in canvas CustomPainter Flutter

I have the coordinates of Point A(x1,y1) and Point B(x2,y2) and I want to extend the line to Point C(x3, y3) upto outer radius in the same angle in canvas CustomPainter Flutter. How to find the value of x3 and y3?. If I get the Point C I can use drawLine function to draw straight from Point A to C
Some basic trigonometry will get what you need:
let A = { x: 10, y: 10 };
let B = { x: 80, y: 20 };
let radius = 100
let angle = Math.atan2(B.y - A.y, B.x - A.x)
let C = {
x: B.x + Math.cos(angle) * radius,
y: B.y + Math.sin(angle) * radius
};
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
ctx.rect(A.x, A.y, 2, 2);
ctx.rect(B.x, B.y, 2, 2);
ctx.rect(C.x, C.y, 2, 2);
ctx.moveTo(A.x, A.y);
ctx.lineTo(C.x, C.y);
ctx.stroke();
<canvas id="c"></canvas>
What we know Points A, B and radius...
with Points A and B we can calculate the angle of the line
that is:
Math.atan2(B.y - A.y, B.x - A.x)
Then with that angle and the radius we can calculate point C:
let C = {
x: B.x + Math.cos(angle) * radius,
y: B.y + Math.sin(angle) * radius
};
Only assumption is the start of the radius, on this case I'm assuming is from point B
All that remains is to draw something with that

Failing to overwrite antialiased pixels in Cairo

While using Cairo 1.14.6 for display purposes, I found that overwriting the very same path with another color does not necessarily overwrite all pixels, and leaves undesirable artifacts behind.
As evidence of my claim I offer this output from a short self-contained example, the source for which follows further below.
An explanation of the six parts of the image, from left to right:
Original shape stroked in blue.
Original shape overwritten in RGBA white.
Original shape overwritten in RGB white.
Original shape overwritten in RGBA white with CAIRO_OPERATOR_SOURCE mode.
Original shape overwritten in RGBA white with CAIRO_OPERATOR_SOURCE mode and CAIRO_ANTIALIAS_NONE.
Original shape overwritten in RGBA white with CAIRO_OPERATOR_SOURCE mode and CAIRO_ANTIALIAS_BEST.
The image was generated from the following code:
#include "cairo/cairo.h"
#define M_PI 3.14159265358979323846
void draw_shape(cairo_t* cr, int x, int y) {
cairo_arc(cr, 50 + x, 50 + y, 48, -M_PI, -M_PI / 2);
cairo_stroke(cr);
cairo_move_to(cr, x + 2, y + 2);
cairo_line_to(cr, x + 48, y + 48);
cairo_stroke(cr);
}
int main(int argc, char** argv) {
int x = 0;
int y = 0;
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 50);
cairo_t* cr = cairo_create(surface);
/* Draw a white background and a few shapes to overwrite */
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
cairo_paint(cr);
cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0);
draw_shape(cr, x, y); x += 50;
draw_shape(cr, x, y); x += 50;
draw_shape(cr, x, y); x += 50;
draw_shape(cr, x, y); x += 50;
draw_shape(cr, x, y); x += 50;
draw_shape(cr, x, y); x += 50;
x = 50;
/* Leftmost shape is left unchanged for reference */
/* Stroke in RGBA opaque white */
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
draw_shape(cr, x, y); x += 50;
/* Stroke in RGB white */
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
draw_shape(cr, x + 0, y); x += 50;
/* Stroke in opaque white without blending */
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
draw_shape(cr, x, y); x += 50;
/* Stroke in opaque white without blending, with no antialiasing */
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
draw_shape(cr, x, y); x += 50;
/* Stroke in opaque white without blending, with best antialiasing */
cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_antialias(cr, CAIRO_ANTIALIAS_BEST);
draw_shape(cr, x, y); x += 50;
/* Write the results to a file */
cairo_surface_write_to_png(surface, "output.png");
return 0;
}
It doesn't make intuitive sense to me that overwriting the very same shape would not overwrite all of its pixels, especially if I force it into non-blending CAIRO_OPERATOR_SOURCE mode. The results are the same on the framebuffer that constitutes my actual surface, so this is not an issue with the backend.
Cairo is usually so good at what it does that I'm very surprised at this. Is there no way to overwrite an anti-aliased shape exactly in Cairo?
What I was trying to do is apparently not possible. I posted my question to the Cairo mailing list and was offered two options:
Keep a copy of the original pixels before drawing over them: "Anti-aliasing involves blending. If you don’t want anti-aliasing, turn it off." (Link)
Draw at a much higher resolution: "The only real solution is to draw a much higher resolution with coverage rounded to exactly zero or one for each pixel." (Link)
More specifically:
All that is stored in the pixel from the first drawing is what
percentage of the pixel was covered by the shape. It does not remember
exactly what parts of the pixel are covered.
Since antialiasing necessarily involves blending, and since Cairo does not remember what part of a subpixel led to the blending, it has no way of knowing how to undo that blending.

I have a line from the center point of a circle to another point. I want to find the point where the line intersects the circumference of the circle

I have tried several different solutions but no luck so far.
- (CGPoint)contractLineTemp:(CGPoint)point :(CGPoint)circle :(float)circleRadius {
CGFloat x,y;
x = point.x - circle.x;
y = point.y - circle.y;
CGFloat theta = atan2(x, y);
CGPoint newPoint;
newPoint.x = circle.x + circleRadius * sin(theta);
newPoint.y = circle.y + circleRadius * cos(theta);
return newPoint;
}
- (CGPoint)contractLineTemp:(CGPoint)startPoint :(CGPoint)endPoint :(float)scaleBy {
float dx = endPoint.x - startPoint.x;
float dy = endPoint.y - startPoint.y;
float scale = scaleBy * Q_rsqrt(dx * dx + dy * dy);
return CGPointMake (endPoint.x - dx * scale, endPoint.y - dy * scale);
}
Both of these solutions kind of work. If I draw the line to the center of the circle you can see that it intersects the circle exactly where it should.
http://www.freeimagehosting.net/le5pi
If I use either of the solutions above and draw to the circumference of the circle depending on the angle it is no longer going towards the center of the circle. In the second image the line should be in the middle of the right edge of the circle and going straight right.
http://www.freeimagehosting.net/53ovs
http://www.freeimagehosting.net/sb3b2
Sorry for the links. I am to new to currently post images.
Thanks for you help.
It's easier to treat this as a vector problem. Your second approach is close, but you don't correctly scale the vector between the two points. It's easier to work with a normalized vector in this case, although you have to assume that the distance between the two points on the line is non-zero.
Given:
double x0 = CIRC_X0; /* x-coord of center of circle */
double y0 = CIRC_Y0; /* y-coord of center of circle */
double x1 = LINE_X1; /* x-coord of other point on the line */
double y1 = LINE_Y1; /* y-coord of other point on the line */
Then the vector between the two points is (vx,vy):
double vx = x1 - x0;
double vy = y1 - y0;
It's easier to work with a unit vector, which we can get by normalizing (vx,vy):
double vmag = sqrt(vx*vx + vy*vy);
vx /= vmag; /* Assumption is vmag > 0 */
vy /= vmag;
Now, any point along the line can be described as:
x0 + dist * vx
y0 + dist * vy
where dist is the distance from the center. The intersection of the circle and the line must be a distance of CIRC_RADIUS from the center, so:
double x_intersect = x0 + CIRC_RADIUS * vx;
double y_intersect = y0 + CIRC_RADIUS * vy;
I think that there may be a convention conflict on what theta, x and y are. The atan2 function yields values in the range -pi..pi, by taking the convention of theta as the angle growing from the X axis towards Y. However you are considering theta as the angle from Y to X.
Try changing the code:
CGFloat theta = atan2(y, x);
CGPoint newPoint;
newPoint.x = circle.x + circleRadius * cos(theta);
newPoint.y = circle.y + circleRadius * sin(theta);
Although your formulae are consistent within a coordinate system, it may have conflict with the screen/display device coordinate system.

Drawing triangle/arrow on a line with CGContext

I am using the framework of route-me for working with locations.
In this code the path between two markers(points) will be drawn as a line.
My Question: "What code should I add if I want to add an arrow in the middle(or top) of the line, so that it points the direction"
Thanks
- (void)drawInContext:(CGContextRef)theContext
{
renderedScale = [contents metersPerPixel];
float scale = 1.0f / [contents metersPerPixel];
float scaledLineWidth = lineWidth;
if(!scaleLineWidth) {
scaledLineWidth *= renderedScale;
}
//NSLog(#"line width = %f, content scale = %f", scaledLineWidth, renderedScale);
CGContextScaleCTM(theContext, scale, scale);
CGContextBeginPath(theContext);
CGContextAddPath(theContext, path);
CGContextSetLineWidth(theContext, scaledLineWidth);
CGContextSetStrokeColorWithColor(theContext, [lineColor CGColor]);
CGContextSetFillColorWithColor(theContext, [fillColor CGColor]);
// according to Apple's documentation, DrawPath closes the path if it's a filled style, so a call to ClosePath isn't necessary
CGContextDrawPath(theContext, drawingMode);
}
- (void) drawLine: (CGContextRef) context from: (CGPoint) from to: (CGPoint) to
{
double slopy, cosy, siny;
// Arrow size
double length = 10.0;
double width = 5.0;
slopy = atan2((from.y - to.y), (from.x - to.x));
cosy = cos(slopy);
siny = sin(slopy);
//draw a line between the 2 endpoint
CGContextMoveToPoint(context, from.x - length * cosy, from.y - length * siny );
CGContextAddLineToPoint(context, to.x + length * cosy, to.y + length * siny);
//paints a line along the current path
CGContextStrokePath(context);
//here is the tough part - actually drawing the arrows
//a total of 6 lines drawn to make the arrow shape
CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context,
from.x + ( - length * cosy - ( width / 2.0 * siny )),
from.y + ( - length * siny + ( width / 2.0 * cosy )));
CGContextAddLineToPoint(context,
from.x + (- length * cosy + ( width / 2.0 * siny )),
from.y - (width / 2.0 * cosy + length * siny ) );
CGContextClosePath(context);
CGContextStrokePath(context);
/*/-------------similarly the the other end-------------/*/
CGContextMoveToPoint(context, to.x, to.y);
CGContextAddLineToPoint(context,
to.x + (length * cosy - ( width / 2.0 * siny )),
to.y + (length * siny + ( width / 2.0 * cosy )) );
CGContextAddLineToPoint(context,
to.x + (length * cosy + width / 2.0 * siny),
to.y - (width / 2.0 * cosy - length * siny) );
CGContextClosePath(context);
CGContextStrokePath(context);
}
The drawing of the actual triangle/arrow is easy once you have two points on your path.
CGContextMoveToPoint( context , ax , ay );
CGContextAddLineToPoint( context , bx , by );
CGContextAddLineToPoint( context , cx , cy );
CGContextClosePath( context ); // for triangle
Getting the points is a little more tricky. You said path was a line, as opposed to a curve or series of curves. That makes it easier.
Use CGPathApply to pick two points on the path. Probably, this is the last two points, one of which may be kCGPathElementMoveToPoint and the other will be kCGPathElementAddLineToPoint. Let mx,my be the first point and nx,ny be the second, so the arrow will point from m towards n.
Assuming you want the arrow at the tip of the line, bx,by from above will equal nx,ny on the line. Choose a point dx,dy between mx,my and nx,ny to calculate the other points.
Now calculate ax,ay and cx,cy such that they are on a line with dx,dy and equidistant from path. The following should be close, although I probably got some signs wrong:
r = atan2( ny - my , nx - mx );
bx = nx;
by = ny;
dx = bx + sin( r ) * length;
dy = by + cos( r ) * length;
r += M_PI_2; // perpendicular to path
ax = dx + sin( r ) * width;
ay = dy + cos( r ) * width;
cx = dx - sin( r ) * width;
cy = dy - cos( r ) * width;
Length is the distance from the tip of the arrow to the base, and width is distance from the shaft to the barbs, or half the breadth of the arrow head.
If path is a curve, then instead of finding mx,my as the previous point or move, it will be the final control point of the final curve. Each control point is on a line tangent to the curve and passing through the adjacent point.
I found this question as I had the same. I took drawnonward's example and it was so close... But with a flipping of cos and sin, I was able to get it to work:
r = atan2( ny - my , nx - mx );
r += M_PI;
bx = nx;
by = ny;
dx = bx + cos( r ) * length;
dy = by + sin( r ) * length;
r += M_PI_2; // perpendicular to path
ax = dx + cos( r ) * width;
ay = dy + sin( r ) * width;
cx = dx - cos( r ) * width;
cy = dy - sin( r ) * width;
Once I did that, my arrows were pointed exactly the wrong way. So I added that second line (r += M_PI;)
Thanks go to drawnonward!
And here is Swift 4+ version for Friedhelm Brügge answer: (I'll draw it on image)
func drawArrow(image: UIImage, ptSrc: CGPoint, ptDest: CGPoint) {
// create context with image size
UIGraphicsBeginImageContext(image.size)
let context = UIGraphicsGetCurrentContext()
// draw current image to the context
image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
var slopY: CGFloat, cosY: CGFloat, sinY: CGFloat;
// Arrow size
let length: CGFloat = 35.0;
let width: CGFloat = 35.0;
slopY = atan2((ptSrc.y - ptDest.y), (ptSrc.x - ptDest.x));
cosY = cos(slopY);
sinY = sin(slopY);
//here is the tough part - actually drawing the arrows
//a total of 6 lines drawn to make the arrow shape
context?.setFillColor(UIColor.white.cgColor)
context?.move(to: CGPoint(x: ptSrc.x, y: ptSrc.y))
context?.addLine(to: CGPoint(x: ptSrc.x + ( -length * cosY - ( width / 2.0 * sinY )), y: ptSrc.y + ( -length * sinY + ( width / 2.0 * cosY ))))
context?.addLine(to: CGPoint(x: ptSrc.x + (-length * cosY + ( width / 2.0 * sinY )), y: ptSrc.y - (width / 2.0 * cosY + length * sinY )))
context?.closePath()
context?.fillPath()
context?.move(to: CGPoint(x: ptSrc.x, y: ptSrc.y))
context?.addLine(to: CGPoint(x: ptDest.x + (length * cosY - ( width / 2.0 * sinY )), y: ptDest.y + (length * sinY + ( width / 2.0 * cosY ))))
context?.addLine(to: CGPoint(x: ptDest.x + (length * cosY + width / 2.0 * sinY), y: ptDest.y - (width / 2.0 * cosY - length * sinY)))
context?.closePath()
context?.fillPath()
// draw current context to image view
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
//close context
UIGraphicsEndImageContext()
}