Can someone explain exactly how rotation of the canvas about a point works ?
I have lines and I want to draw text parallel to each line. I have worked out the trigonometry required to calculate the angle of the line and it`s centre point.
When I try rotate the canvas about the start point of the line, then draw the text and restore, I am always getting strange offsets which obviously means I do not quiet get how the rotation is working ...
Can someone explain what happens when you rotate the canvas about a point and how the then drawing on co-ords X,Y translates back ?
see the following class:
class V extends View {
private Paint mPaint;
public V(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xffeeeeee);
mPaint.setTextSize(24);
}
#Override
protected void onDraw(Canvas canvas) {
float x = 100;
float y = 50;
float dx = 60;
float dy = 40;
canvas.drawLine(x, y, x + dx, y + dy, mPaint);
canvas.save();
float degrees = (float) (180 * Math.atan2(dy, dx) / Math.PI);
canvas.rotate(degrees, x, y);
canvas.drawText("text", x, y, mPaint);
canvas.restore();
}
}
now i hope everything should be clear how canvas rotation works...
Related
guys
I try to implement an black arrow which is on head of arc. The arrow animates with the arc, and has circle trajectory. I already implement the red arc with animation based on different values.
Please see the attachment.
How can I implement the black arrow on top of red arc? since if I do the same way as red arc animation, the black arrow will print the trajectory which is not desired.
Thanks in advance!
Leon
all you need is Canvas.
Here is example.
This is the your draw class:
public class CircleView extends View
{
public CircleView(Context context) {
super(context);
path = new Path();
paint = new Paint();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float angle = 270;
float radius = canvas.getWidth()/3;
float x = canvas.getWidth()/2;
float y = canvas.getHeight()/2;
final RectF oval = new RectF();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(25);
oval.set(x - radius, y - radius, x + radius,y + radius);
// Draw circle
paint.setColor(Color.RED);
canvas.drawArc(oval, 135, angle, false, paint);
paint.setColor(Color.BLUE);
canvas.drawArc(oval, angle, 405-angle, false, paint);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
float l = 1.2f;
float a = angle*(float)Math.PI/180;
// Draw arrow
path.moveTo(x+ (float)Math.cos(a) *radius, y + (float)Math.sin(a) * radius);
path.lineTo(x+ (float)Math.cos(a+0.1) *radius*l, y + (float)Math.sin(a+0.1) * radius*l);
path.lineTo(x+ (float)Math.cos(a-0.1) *radius*l, y + (float)Math.sin(a-0.1) * radius*l);
path.lineTo(x+ (float)Math.cos(a) *radius, y + (float)Math.sin(a) * radius);
canvas.drawPath(path, paint);
}
private Path path;
private Paint paint;
}
This is activity for it:
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new CircleView(this));
}
}
For angle == 200 you will see image like this:
Working copy for IntelliJ Idea: https://github.com/weerf/android_circle
I had an android app that displays a scale and it worked fine on all version until 5.0 (Lollipop) where the all the texts drawn with drawText method are somehow truncated.
Only the second letter of a tow letters text is displayed.
i.e. The first mark is only '0' letter but is not displayed.
For the second 2 marks (40, and 80) only 0 is displayed and for the rest, 140... 250, no text is displayed.
The text size seems to be OK but not all the chars are displayed.
The image is scaled in [0..1, 0..1] square.
I have found and try lot of posts on drawText but none of them helped.
(View.setLayerType() all combinations, setLinearText(true), Paint.Style.FILL...)
private void drawScale(final Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
// On canvas, North is 0 degrees, East is 90 degrees, South is 180 etc.
// We start the scale somewhere South-West so we need to first rotate the canvas.
canvas.rotate(mScaleRotation, 0.5f, 0.5f);
final int totalTicks = mDivisions * mSubdivisions + 1;
for (int i = 0; i < totalTicks; i++) {
final float y1 = mScaleRect.top;
final float y2 = y1 + 0.015f; // height of division
final float y3 = y1 + 0.045f; // height of subdivision
final float value = getValueForTick(i);
final Paint paint = getRangePaint(value);
if (0 == value % mDivisions) {
// Draw a division tick
canvas.drawLine(0.5f, y1, 0.5f, y3, paint);
// Draw the text 0.15 away from the division tick
canvas.drawText(valueString(value), 0.5f, y3 + 0.045f, paint);
}
else {
// Draw a subdivision tick
canvas.drawLine(0.5f, y1, 0.5f, y2, paint);
}
canvas.rotate(mSubdivisionAngle, 0.5f, 0.5f);
}
canvas.restore();
}
where a range paint is create like this
mRangePaints[i] = new Paint(Paint.LINEAR_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mRangePaints[i].setColor(mRangeColors[i]);
mRangePaints[i].setStyle(Paint.Style.STROKE);
mRangePaints[i].setStrokeWidth(0.005f);
mRangePaints[i].setTextSize(0.05f);
mRangePaints[i].setTypeface(Typeface.SANS_SERIF);
mRangePaints[i].setTextAlign(Align.CENTER);
mRangePaints[i].setShadowLayer(0.005f, 0.002f, 0.002f, mTextShadowColor);
and canvas is created like this
mBackground = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(mBackground);
This is a capture from and android 5.0
Android 5.0
This is capture from an android 4.4
Android <5.0
Any help, clue, etc... will be deeply appreciated.
EDIT:
Thank you for suggestions,
I have wrote a function that re-scale all the sizes as for an textSize 10f.
Every original call to canvas.drawText was replaced with a call to this
drawScaledText(canvas, ... original values ...)
Everything seem to work fine.
Here is the code snippet:
public static void drawScaledText(Canvas canvas, String text, float x, float y, Paint paint, float scale) {
float originalStrokeWidth = paint.getStrokeWidth();
float originalTextSize = paint.getTextSize();
float textScaling = 10f/originalTextSize;
paint.setStrokeWidth(originalStrokeWidth * textScaling);
paint.setTextSize(originalTextSize * textScaling);
canvas.save();
canvas.scale(scale/textScaling, scale/textScaling);
canvas.drawText(text, x * textScaling, y * textScaling, paint);
canvas.restore();
paint.setStrokeWidth(originalStrokeWidth);
paint.setTextSize(originalTextSize);
}
canvas.DrawText() and canvas.DrawTextOnPath() do not work well at sizes under 1
Increase your font size. You can scale your canvas to compensate.
See answer to this question.
Android 4.2.1 wrong character kerning (spacing)
I'm trying to draw the spectrum of an audio file on a circle. Like this:
So on the circle I just want rectangles drawn like you see on the image.
I've got this code:
public void onRender(Canvas canvas, FFTData data, Rect rect) {
canvas.drawCircle(rect.width()/2, rect.height()/2, 200, mPaint);
for (int i = 0; i < data.bytes.length / mDivisions; i++) {
byte rfk = data.bytes[mDivisions * i];
byte ifk = data.bytes[mDivisions * i + 1];
float magnitude = (rfk * rfk + ifk * ifk);
int dbValue = (int) (10 * Math.log10(magnitude));
}
}
Where FFTData is the Fast Fourier Transformation data that Android gives me. Now in my dbValue I got the strength of the signal. mDivisions is how much bars I want. Currently set on 16 because I don't know how much I can set on the circle.
I'm stuck on how I can draw the rectangle with his center on the circle line... So I want a rectangle whose height is based on the dbValue so that I get high and low rectangles. And the center must be placed on my circle line.
Can someone help me on this math formula?
Run a loop over all 360 degrees of the circle (at wanted step), and, for each point, convert Polar (this angle and the radius of the circle) coordinates into Cartesian, as described here, for instance. This way you get the location of the centre of your rectangle.
Translate the system of the coordinates, making origin to be at the wanted point on the circle line and then rotate by the circle angle at that point.
Alternatively, you can build a trapezoid by getting corners at angle +- some offset and radius +- some offset (proportional to your value to plot). It will have shorter inner edge and longer outer edge. Such trapezoids may look better if painted side by side.
i think all you have needed is a pencil and a paper and a little math and also some free time to play :-)
public class MainActivity extends Activity {
ImageView drawingImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawingImageView = (ImageView) this.findViewById(R.id.DrawingImageView);
Paint paint;
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(16);
final Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
int centerX =400;
int centerY =400;
int R = 200;
canvas.drawCircle(centerX, centerY, R, paint);
int h = 100;
paint.setColor(Color.RED);
Path p = new Path();
p.moveTo(centerX + R - h/2, centerY);
p.lineTo(centerX + R + h/2, centerY);
canvas.drawPath(p, paint);
p = mySpectrumDrawer(centerX,centerY,R,h,15);
canvas.drawPath(p, paint);
h = 50;
p = mySpectrumDrawer(centerX,centerY,R,h,30);
canvas.drawPath(p, paint);
h = 60;
p = mySpectrumDrawer(centerX,centerY,R,h,60);
canvas.drawPath(p, paint);
h = 80;
p = mySpectrumDrawer(centerX,centerY,R,h,90);
canvas.drawPath(p, paint);
drawingImageView.setImageBitmap(bitmap);
}
private Path mySpectrumDrawer(int centerX, int centerY,int R,int height, int angel){
Path p = new Path();
int dX = (int) (R*(Math.cos(Math.toRadians(angel))));
int dY = (int) (R*(Math.sin(Math.toRadians(angel))));
int dhx = (int) (height/2*(Math.cos(Math.toRadians(angel))));
int dhy = (int) (height/2*(Math.sin(Math.toRadians(angel))));
p.moveTo(centerX + dX - dhx , centerY - dY + dhy);
p.lineTo(centerX + dX + dhx , centerY - dY - dhy);
return p;
}
}
I have following code:
protected void onDraw(Canvas canvas)
{
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
xRatio = getWidth()*1.0f / picWidth;
yRatio = getHeight()*1.0f / picHeight;
canvas.drawBitmap( sourceImage, null , new Rect(0,0,getWidth(),getHeight()),paint);
for (int i = 0; i < eyesMidPts.length; i++)
{
if (eyesMidPts[i] != null)
{
// where x and y are eyes mid point coordinates
float x = eyesMidPts[i].x*xRatio;
float y = eyesMidPts[i].y*yRatio;
float radius = (float) (eyesDistance[i]);
float left = x - radius;
float right = x + radius;
float top = y - radius;
// we want to increase the bottom radius by double to get other half of the face.
float bottom = (float) (y + radius * 2);
paint.setStrokeWidth(eyesDistance[i] /20);
RectF ovalBounds = new RectF();
ovalBounds.set(left, top, right, bottom);
canvas.drawOval(ovalBounds, paint);
}
}
}
Code encompass full face if face is straight. But does not get complete circle around if face is tilted. I am not sure how euler angels work but am hoping it is to get tilt on the face detection. Can someone please help me with this show me some example code so that circle encompasses whole face.
Why do you use RectF to draw a circle? Is it a 'true' circle, or is it really oval / ellipse?
If it's just a circle, why don't you use this.
canvas.drawCircle(x, y, radius, paint);
So you won't need to transform Rectf's points if it's tilted :)
update:
I believe this should work, I don't have an android workflow setup with me to really test it; sorry if it's a miss.
Matrix m = new Matrix(); //creates identity matrix
m.setRotate(tilt_angle); //rotate it by the tilt angle
m.mapRect(ovalBounds); //transform the rect
// RectF face: face position and dimentions
// Create Bitmap where to draw an oval
Bitmap ovalBmp = Bitmap.createBitmap( face.width(), face.height(), config );
canvasBmp = new Canvas(ovalBmp); // Get a canvas to draw an oval on the Bitmap
canvasBmp.drawOval( new RectF( 0, 0, face.width(), face.height ), paint );
// Create transformation matrix
transforMatrix = new Matrix();
// Rotate around center of oval
transforMatrix.postRotate( tilt_angle, face.width() / 2, face.height() / 2 );
transforMatrix.postTranslate( face.left, face.top );
canvas.drawBitmap( ovalBmp, transforMatrix, null );
ps: I assume by tilt angle you mean roll, where pitch is rotation around X axis, yaw is rotation around Y axis and roll is rotation around Z axis.
I want to draw an arc of ellipse in my android app.
I used this code:
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
angle=45; //angle of ellipse major axis with X-axis.
startAngle=0; //start angle of arc
sweepAngle=90; //sweep angle of arc
a = 200; //major axis of ellipse
b = 100; //minor axis of ellipse
canvas.rotate(angle, center.x, center.y);
//draw the arc
canvas.drawArc(rect, startAngle - angle, sweepAngle, true, paintLine);
paintLine.setPathEffect(new DashPathEffect(new float[] { 5, 5 }, 0));
canvas.drawOval(rect, paintLine);
canvas.restore();
paintLine.setPathEffect(null);
}
I receive this shape:
The arc I need should start and end at the red point at this image:
Please tell me what mistake did I make.
Thanks.
When android draws an elliptic arc, it is as if it does so by first drawing a circular arc over the supplied sweep angle. Then it scales this circular arc so that it fits the specified ellipse. The the start and sweep-angles of the ellipse will be different from the start and sweep-angles specified. To get a wanted-angle, one must specify an actual-angle as tan(actual-angle) = (a/b)*tan(wanted-angle)
public class Main3Activity extends Activity {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = new View(this) {
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
float angle=45; //angle of ellipse major axis with X-axis.
// These are the wanted angles
float startAngle_ = (float)Math.toRadians(315); //start angle of arc
float endAngle_ = (float)Math.toRadians(45); //end angle of arc
float a = 200; //major axis of ellipse
float b = 100; //minor axis of ellipse
float xc = getWidth()/2f;
float yc = getHeight()/2f;
// These are the angles to use
float startAngle = (float)Math.toDegrees(
Math.atan2(a*Math.sin(startAngle_),b*Math.cos(startAngle_)));
float endAngle = (float)Math.toDegrees(
Math.atan2(a*Math.sin(endAngle_),b*Math.cos(endAngle_)));
float sweepAngle = endAngle - startAngle;
RectF rect = new RectF(xc-a,yc-b,xc+a,yc+b);
Paint paintLine = new Paint(Paint.ANTI_ALIAS_FLAG);
paintLine.setStyle(Paint.Style.STROKE);
canvas.rotate(angle,xc,yc);
//draw the arc
canvas.drawArc(rect, startAngle, sweepAngle, true, paintLine);
paintLine.setPathEffect(new DashPathEffect(new float[] { 5, 5 }, 0));
canvas.drawOval(rect, paintLine);
canvas.restore();
paintLine.setPathEffect(null);
}
};
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(view,layout);
}
This question is nearly seven years old! About time to update the documentation?