TrueType parsing in Java? - java

Is there a simple library that can parse a True Type font file and give me a list of vectors/points for me to render it myself? I'm talking about something similar to freetype, but for Java.

Java can do that out of the box. Some time ago, I explored this field.
I used Processing for easy/fast graphic rendering, but the code to access font information is pure Java. So maybe my little program can be of interest for you. I just pasted it in Pastebin
/**
Get a font and display glyphs with additional information
(bounding boxes, anchor & control points...)
by Philippe Lhoste <PhiLho(a)GMX.net> http://Phi.Lho.free.fr & http://PhiLho.deviantART.com
*/
/* File/Project history:
1.01.000 -- 2008/08/28 (PL) -- Some improvements.
1.00.000 -- 2008/08/28 (PL) -- Creation.
*/
/* Copyright notice: For details, see the following file:
http://Phi.Lho.free.fr/softwares/PhiLhoSoft/PhiLhoSoftLicence.txt
This program is distributed under the zlib/libpng license.
Copyright (c) 2008 Philippe Lhoste / PhiLhoSoft
*/
import java.awt.font.*;
import java.awt.geom.*;
final static int DEMO_ID = 2;
final static String FONT_NAME = "Times New Roman";
final static String STRING = "p#";
int FONT_SIZE, SCALE;
Graphics2D g2;
AffineTransform transform;
HashMap colorList = new HashMap();
void setup()
{
size(1000, 700);
smooth();
noLoop();
background(150);
noFill();
g2 = ((PGraphicsJava2D) g).g2;
// Move drawing to a convenient place
transform = new AffineTransform();
if (DEMO_ID == 1)
{
FONT_SIZE = 1;
SCALE = 300;
transform.translate(40, 320);
transform.scale(SCALE, SCALE);
}
else if (DEMO_ID == 2)
{
FONT_SIZE = 2;
SCALE = 300;
transform.translate(50, 500);
transform.scale(SCALE, SCALE);
}
else if (DEMO_ID == 3)
{
FONT_SIZE = 2;
SCALE = 300;
transform.translate(50, 500);
transform.scale(SCALE, SCALE);
}
// g2.setTransform(transform);
// And show the origin (and scale) of drawing
SetColor(#FF0000); // Update g2
strokeWeight(5);
Line2D.Double line = new Line2D.Double(-0.1, -0.1, 0.1, 0.1);
g2.draw(transform.createTransformedShape(line));
line = new Line2D.Double(-0.1, 0.1, 0.1, -0.1);
g2.draw(transform.createTransformedShape(line));
strokeWeight(2);
// Now, we get the font
Font font = new Font(FONT_NAME, Font.PLAIN, FONT_SIZE);
g2.setFont(font);
FontRenderContext frc = g2.getFontRenderContext();
if (DEMO_ID == 1)
{
//~ String str = "%&#";
GlyphVector glyphVector = font.createGlyphVector(frc, STRING);
for (int i = 0; i < STRING.length(); i++)
{
strokeWeight(2);
SetColor(#00FF00);
Shape vbs = glyphVector.getGlyphVisualBounds(i);
g2.draw(transform.createTransformedShape(vbs));
SetColor(#005000);
Rectangle r = glyphVector.getGlyphPixelBounds(i, null, 0.0f, 0.0f);
g2.draw(transform.createTransformedShape(r));
SetColor(#A0A000);
Shape lbs = glyphVector.getGlyphLogicalBounds(i);
g2.draw(transform.createTransformedShape(lbs));
SetColor(#0050F0);
strokeWeight(5);
Shape shape = glyphVector.getGlyphOutline(i);
g2.draw(transform.createTransformedShape(shape));
}
// Draw the whole string at once
SetColor(#F0A000);
strokeWeight(1);
Shape shape = glyphVector.getOutline();
g2.draw(transform.createTransformedShape(shape));
}
else if (DEMO_ID == 2)
{
GlyphVector glyphVector = font.createGlyphVector(frc, STRING);
for (int i = 0; i < STRING.length(); i++)
{
SetColor(#0050F0);
strokeWeight(5);
Shape shape = glyphVector.getGlyphOutline(i);
g2.draw(transform.createTransformedShape(shape));
HighlightPoints(shape);
}
}
else if (DEMO_ID == 3) // To test the SEG_CUBICTO case!
{
SetColor(#0050F0);
strokeWeight(5);
GeneralPath shape = new GeneralPath();
shape.moveTo(1.5, 0.0);
shape.lineTo(1.8, 0.0);
shape.quadTo(2.0, 0.3, 1.5, 0.3);
shape.lineTo(1.1, 0.3);
shape.curveTo(0.7, 0.3, 0.5, -0.5, 1.2, -0.2);
shape.lineTo(1.5, -0.3);
shape.closePath();
g2.draw(transform.createTransformedShape(shape));
HighlightPoints(shape);
}
}
void HighlightPoints(Shape shape)
{
strokeWeight(1);
PathIterator iterator = shape.getPathIterator(null);
float[] coords = new float[6];
float px = 0, py = 0;
while (!iterator.isDone())
{
int type = iterator.currentSegment(coords);
switch (type)
{
case PathIterator.SEG_MOVETO: // One point
print("Move ");
px = coords[0];
py = coords[1];
DrawMovePoint(px, py);
break;
case PathIterator.SEG_LINETO:
print("Line ");
px = coords[0];
py = coords[1];
DrawLinePoint(px, py);
break;
case PathIterator.SEG_QUADTO: // Two points
print("Quad ");
DrawControlLine(coords[0], coords[1], px, py);
px = coords[2];
py = coords[3];
DrawControlLine(coords[0], coords[1], px, py);
DrawControlPoint(coords[0], coords[1]);
DrawQuadPoint(px, py);
break;
case PathIterator.SEG_CUBICTO: // Three points
print("Cubic "); // Not seen yet...
DrawControlLine(coords[0], coords[1], px, py); // Connect to last point
px = coords[4];
py = coords[5];
DrawControlLine(coords[2], coords[3], px, py);
DrawControlPoint(coords[0], coords[1]);
DrawControlPoint(coords[2], coords[3]);
DrawCubicPoint(px, py);
break;
case PathIterator.SEG_CLOSE: // No points
}
iterator.next();
}
}
void DrawMovePoint(float x, float y)
{
float radius = 2.5 / SCALE;
Ellipse2D.Float e = new Ellipse2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
SetColor(#FF00A0);
g2.fill(transform.createTransformedShape(e));
SetColor(#FF00FF);
g2.draw(transform.createTransformedShape(e));
}
void DrawLinePoint(float x, float y)
{
float radius = 4.5 / SCALE;
Ellipse2D.Float e = new Ellipse2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
SetColor(#FF00FF);
g2.draw(transform.createTransformedShape(e));
}
void DrawQuadPoint(float x, float y)
{
SetColor(#80FF00);
float radius = 3.0 / SCALE;
Rectangle2D.Float r = new Rectangle2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
g2.draw(transform.createTransformedShape(r));
}
void DrawCubicPoint(float x, float y)
{
SetColor(#FF8000);
float radius = 5.0 / SCALE;
Rectangle2D.Float r = new Rectangle2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
g2.draw(transform.createTransformedShape(r));
}
void DrawControlPoint(float x, float y)
{
SetColor(#D0E000);
float radius = 3.0 / SCALE;
Ellipse2D.Float e = new Ellipse2D.Float(x - radius, y - radius, 2 * radius, 2 * radius);
g2.draw(transform.createTransformedShape(e));
}
void DrawControlLine(float x1, float y1, float x2, float y2)
{
SetColor(#FFA000);
Line2D.Float l = new Line2D.Float(x1, y1, x2, y2);
g2.draw(transform.createTransformedShape(l));
}
Color GetColor(color c)
{
Integer ic = Integer.valueOf(c); // New to 1.5! Cache values
Color k = (Color) colorList.get(ic);
if (k == null)
{
k = new Color(ic);
colorList.put(ic, k);
}
return k;
}
void SetColor(color c)
{
Color k = GetColor(c);
g2.setPaint(k);
}

You can use Batik to parse a TrueType Font and generate an SVG file (SVGFont). So I bet that you could use the libraries to get your vectors out.

Related

java android path animation

So I am nearly at the end.
I have been researching the whole day on how to do this.
Draw a path which grows(animation) from one point to another.
I have tried it with Matrix, but that just ended with turning my whole paths.
Here is a image of my project:
my project
My goal is to draw a animated path from one circle to the other.
Code:
public void init(#Nullable AttributeSet attr) {
circle = new Paint();
circle.setColor(Color.GREEN);
circle.setStyle(Paint.Style.FILL);
circle.setAntiAlias(true);
line = new Paint();
line.setColor(Color.GREEN);
line.setStyle(Paint.Style.STROKE);
line.setStrokeWidth(10);
line.setAntiAlias(true);
Collections.addAll(height, 100, 20, 50, 40, 70, 10, 50); // in percent
System.out.println(height.size() + " this is the size");
}
#Override
protected void onDraw(Canvas canvas) {
float y = getHeight() / 20 * 14;
float x = getWidth() / 8;
float radius = (canvas.getWidth() * canvas.getHeight()) / 40940;
for (int c = 1; c < 8; c++) {
System.out.println("at " + c);
canvas.drawCircle(x * c, y - ((getHeight() / 20) * (height.get(c - 1) / 10)), radius, circle);
points.add(new PointF(x * c, (y - ((getHeight() / 20) * (height.get(c - 1) / 10)))));
}
}
Please Help,
Thanks
you just need animate the path using ValueAnimator
create one path object
Path path = new Path();
and create animator
ValueAnimator animator = new ValueAnimator();
float startX = // starting circle x co-ordinate
float startY = // starting circle y co-ordinate
float endX = // end circle x co-ordinate
float endY = // end circle y co-ordinate
PropertyValuesHolder propertyX = PropertyValuesHolder.ofFloat("x",startX,endX);
PropertyValuesHolder propertyY = PropertyValuesHolder.ofFloat("y",startY,endY);
valueAnimator.setValues(propertyX,propertyY);
valueAnimator.setDuration(1000); // animation time
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float x = (float) animation.getAnimatedValue("x");
float y = (float) animation.getAnimatedValue("y");
path.lineTo(x,y);
invalidate();
}
});
valueAnimator.start();
and in onDraw() draw the path
canvas.drawPath(path,paint);

Animating Circle to Move Across Line

I'm using the lerp() function to move my circle across a line, but it isn't working. The circle always ends up on the line somewhere depending on what my amt parameter is for the lerp() function. If I put 0.5 for the amt then the circle is placed half way down the line, but I can't see it move nor does the circle finish moving down the length of the circle. So can anyone help me make the circle move down the line?
float x1,y1,x2,y2;
float cx,cy;
float x4,y4;
void setup() {
size(600,600);
x1 = 200;
y1 = 150;
x2 = 300;
y2 = 250;
cx = 450;
cy = 200;
}
void draw() {
background(60);
stroke(220);
line(x1,y1,x2,y2);
noFill();
noStroke();
// calculate the point
float k = ((y2-y1) * (cx-x1) - (x2-x1) * (cy-y1))
/ ((y2-y1)*(y2-y1) + (x2-x1)*(x2-x1));
float x4 = cx - k * (y2-y1);
float y4 = cy + k * (x2-x1);
stroke(0);
line(cx,cy,x4,y4); //line connecting circle and point on line
float x = lerp(cx, x4, .1);
float y = lerp(cy, y4, .1);
fill(255, 0, 175);
ellipse(x4,y4, 8,8);
fill(175, 0, 255);
ellipse(x, y, 50, 50);
}
You need to use a variable for the amount value passed into the lerp() function. Then just increase that variable over time to animate:
float amount = 0;
float speed = .001;
void setup() {
size(500, 500);
}
void draw() {
float startX = 0;
float startY = 0;
float endX = width;
float endY = height;
float currentX = lerp(startX, endX, amount);
float currentY = lerp(startY, endY, amount);
background(0);
ellipse(currentX, currentY, 20, 20);
amount += speed;
}

Getting a line to move along the tangent line of a circle in Processing

I have a point following the path of a circle, and at a determined time, I want that point to "break" off and travel along the tangent line. How do I find this? I've been told that the derivative is
x = -sin(time)
and
y = -sin(time)
(not sure if I understand the "time" part of what I was told), but I don't see how that is enough to get my point to travel along this line. Any tips? Here is what I have currently.
/*
Rotor draws circle for random period of time, then moves
in a straight direction for a random period of time, beginning a
new circle
*/
Rotor r;
float timer = 0;
boolean freeze = false;
void setup() {
size(1000, 600);
smooth();
noFill();
frameRate(60);
background(255);
timeLimit();
r = new Rotor(100, 100, random(40, 100));
}
void draw() {
timer = timer + frameRate/1000;
if(timer > timeLimit()) {
timer = 0;
timeLimit();
if(freeze == true) {
freeze = false;
} else {
freeze = true;
}
}
if(!freeze) {
r.drawRotor();
} else {
r.holdSteady();
}
}
float timeLimit() {
float timeLimit = random(100);
return timeLimit;
}
Rotor Class:
class Rotor {
color c;
int thickness;
float xPoint;
float yPoint;
float nXPoint;
float nYPoint;
float radius;
float angle = 0;
float centerX;
float centerY;
float pointSpeed = frameRate/100;
Rotor(float cX, float cY, float rad) {
c = color(0);
thickness = 1;
stroke(c);
strokeWeight(thickness);
centerX = cX;
centerY = cY;
radius = rad;
}
void drawRotor() {
angle = angle + pointSpeed;
xPoint = centerX + cos(angle) * radius;
yPoint = centerY + sin(angle) * radius;
ellipse(xPoint, yPoint, thickness, thickness);
strokeWeight(2);
ellipse(centerX, centerY, 5, 5);
}
void holdSteady() {
xPoint = -sin(angle);//need tangent of circle
yPoint = -cos(angle);
ellipse(xPoint, yPoint, 4, 4);
//then set new center x and y
}
void drawNewRotor(float cX, float cy, float rad) {
}
}
You can use tan()
int f =100;
size(300,300);
stroke(0);
translate(width/2, height/2);
for(int i = 0; i< 360; i++){
point(cos(radians(i))*f,sin(radians(i))*f);
point(f,tan(radians(i))*f);
point(tan(radians(i))*f,f);
}

Java Draw Arc Between 2 Points

I'm having trouble drawing the smallest arc described by 3 points: the arc center, an "anchored" end point, and a second point that gives the other end of the arc by determining a radius. I used the law of cosines to determine the length of the arc and tried using atan for the starting degree, but the starting position for the arc is off.
I managed to get the arc to lock onto the anchor point (x1,y1) when it's in Quadrant 2, but that will only work when it is in Quadrant 2.
Solutions I can see all have a bunch of if-statements to determine the location of the 2 points relative to each other, but I'm curious if I'm overlooking something simple. Any help would be greatly appreciated.
SSCCE:
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.*;
import java.awt.*;
import java.util.*;
class Canvas extends JComponent {
float circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2;
Random random = new Random();
public Canvas() {
//Setup.
x1 = random.nextInt(250);
y1 = random.nextInt(250);
//Cant have x2 == circleX
while (x1 == 150 || y1 == 150)
{
x1 = random.nextInt(250);
y1 = random.nextInt(250);
}
circleX = 150; //circle center is always dead center.
circleY = 150;
//Radius between the 2 points must be equal.
dx = Math.abs(circleX-x1);
dy = Math.abs(circleY-y1);
//c^2 = a^2 + b^2 to solve for the radius
radius = (float) Math.sqrt((float)Math.pow(dx, 2) + (float)Math.pow(dy, 2));
//2nd random point
x2 = random.nextInt(250);
y2 = random.nextInt(250);
//I need to push it out to radius length, because the radius is equal for both points.
dx2 = Math.abs(circleX-x2);
dy2 = Math.abs(circleY-y2);
radius2 = (float) Math.sqrt((float)Math.pow(dx2, 2) + (float)Math.pow(dy2, 2));
dx2 *= radius/radius2;
dy2 *= radius/radius2;
y2 = circleY+dy2;
x2 = circleX+dx2;
//Radius now equal for both points.
}
public void paintComponent(Graphics g2) {
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL));
Arc2D.Float centerPoint = new Arc2D.Float(150-2,150-2,4,4, 0, 360, Arc2D.OPEN);
Arc2D.Float point1 = new Arc2D.Float(x1-2, y1-2, 4, 4, 0, 360, Arc2D.OPEN);
Arc2D.Float point2 = new Arc2D.Float(x2-2, y2-2, 4, 4, 0, 360, Arc2D.OPEN);
//3 points drawn in black
g.setColor(Color.BLACK);
g.draw(centerPoint);
g.draw(point1);
g.draw(point2);
float start = 0;
float distance;
//Form a right triangle to find the length of the hypotenuse.
distance = (float) Math.sqrt(Math.pow(Math.abs(x2-x1),2) + Math.pow(Math.abs(y2-y1), 2));
//Law of cosines to determine the internal angle between the 2 points.
distance = (float) (Math.acos(((radius*radius) + (radius*radius) - (distance*distance)) / (2*radius*radius)) * 180/Math.PI);
float deltaY = circleY - y1;
float deltaX = circleX - x1;
float deltaY2 = circleY - y2;
float deltaX2 = circleX - x2;
float angleInDegrees = (float) ((float) Math.atan((float) (deltaY / deltaX)) * 180 / Math.PI);
float angleInDegrees2 = (float) ((float) Math.atan((float) (deltaY2 / deltaX2)) * 180 / Math.PI);
start = angleInDegrees;
//Q2 works.
if (x1 < circleX)
{
if (y1 < circleY)
{
start*=-1;
start+=180;
} else if (y2 > circleX) {
start+=180;
start+=distance;
}
}
//System.out.println("Start: " + start);
//Arc drawn in blue
g.setColor(Color.BLUE);
Arc2D.Float arc = new Arc2D.Float(circleX-radius, //Center x
circleY-radius, //Center y Rotates around this point.
radius*2,
radius*2,
start, //start degree
distance, //distance to travel
Arc2D.OPEN); //Type of arc.
g.draw(arc);
}
}
public class Angle implements MouseListener {
Canvas view;
JFrame window;
public Angle() {
window = new JFrame();
view = new Canvas();
view.addMouseListener(this);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setBounds(30, 30, 400, 400);
window.getContentPane().add(view);
window.setVisible(true);
}
public static void main(String[] a) {
new Angle();
}
#Override
public void mouseClicked(MouseEvent arg0) {
window.getContentPane().remove(view);
view = new Canvas();
window.getContentPane().add(view);
view.addMouseListener(this);
view.revalidate();
view.repaint();
}
#Override
public void mouseEntered(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent arg0) {
// TODO Auto-generated method stub
}
}
Perhaps this will help. It tests with click and drag to set the two points rather than random numbers. It's considerably simpler than what you were attempting and other solutions posted so far.
Notes:
Math.atan2() is a friend in problems like this.
Little helper functions make it easier to reason about your code.
It's best practice to use instance variables for independent values only and compute the dependent values in local variables.
My code fixes some Swing usage problems like calling Swing functions from the main thread.
Code follows:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
class TestCanvas extends JComponent {
float x0 = 150f, y0 = 150f; // Arc center. Subscript 0 used for center throughout.
float xa = 200f, ya = 150f; // Arc anchor point. Subscript a for anchor.
float xd = 150f, yd = 50f; // Point determining arc angle. Subscript d for determiner.
// Return the distance from any point to the arc center.
float dist0(float x, float y) {
return (float)Math.sqrt(sqr(x - x0) + sqr(y - y0));
}
// Return polar angle of any point relative to arc center.
float angle0(float x, float y) {
return (float)Math.toDegrees(Math.atan2(y0 - y, x - x0));
}
#Override
protected void paintComponent(Graphics g0) {
Graphics2D g = (Graphics2D) g0;
// Can always draw the center point.
dot(g, x0, y0);
// Get radii of anchor and det point.
float ra = dist0(xa, ya);
float rd = dist0(xd, yd);
// If either is zero there's nothing else to draw.
if (ra == 0 || rd == 0) { return; }
// Get the angles from center to points.
float aa = angle0(xa, ya);
float ad = angle0(xd, yd); // (xb, yb) would work fine, too.
// Draw the arc and other dots.
g.draw(new Arc2D.Float(x0 - ra, y0 - ra, // box upper left
2 * ra, 2 * ra, // box width and height
aa, angleDiff(aa, ad), // angle start, extent
Arc2D.OPEN));
dot(g, xa, ya);
// Use similar triangles to get the second dot location.
float xb = x0 + (xd - x0) * ra / rd;
float yb = y0 + (yd - y0) * ra / rd;
dot(g, xb, yb);
}
// Some helper functions.
// Draw a small dot with the current color.
static void dot(Graphics2D g, float x, float y) {
final int rad = 2;
g.fill(new Ellipse2D.Float(x - rad, y - rad, 2 * rad, 2 * rad));
}
// Return the square of a float.
static float sqr(float x) { return x * x; }
// Find the angular difference between a and b, -180 <= diff < 180.
static float angleDiff(float a, float b) {
float d = b - a;
while (d >= 180f) { d -= 360f; }
while (d < -180f) { d += 360f; }
return d;
}
// Construct a test canvas with mouse handling.
TestCanvas() {
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
}
// Listener changes arc parameters with click and drag.
MouseInputAdapter mouseListener = new MouseInputAdapter() {
boolean mouseDown = false; // Is left mouse button down?
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = true;
xa = xd = e.getX();
ya = yd = e.getY();
repaint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = false;
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (mouseDown) {
xd = e.getX();
yd = e.getY();
repaint();
}
}
};
}
public class Test extends JFrame {
public Test() {
setSize(400, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().add(new TestCanvas());
}
public static void main(String[] args) {
// Swing code must run in the UI thread, so
// must invoke setVisible rather than just calling it.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().setVisible(true);
}
});
}
}
package curve;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class Main
{
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException
{
PointF pFrom = new PointF(-10f, 30.0f);
PointF pTo = new PointF(-100f, 0.0f);
List<PointF> points = generateCurve(pFrom, pTo, 100f, 7f, true, true);
System.out.println(points);
// Calculate the bounds of the curve
Rectangle2D.Float bounds = new Rectangle2D.Float(points.get(0).x, points.get(0).y, 0, 0);
for (int i = 1; i < points.size(); ++i) {
bounds.add(points.get(i).x, points.get(i).y);
}
bounds.add(pFrom.x, pFrom.y);
bounds.add(pTo.x, pTo.y);
BufferedImage img = new BufferedImage((int) (bounds.width - bounds.x + 50), (int) (bounds.height - bounds.y + 50), BufferedImage.TYPE_4BYTE_ABGR_PRE);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.translate(25.0f - bounds.getX(), 25.0f - bounds.getY());
g.setStroke(new BasicStroke(1.0f));
g.setColor(Color.DARK_GRAY);
g.drawLine(-1000, 0, 1000, 0);
g.drawLine(0, -1000, 0, 1000);
g.setColor(Color.RED);
for (int i = 0; i < points.size(); ++i) {
if (i > 0) {
Line2D.Float f = new Line2D.Float(points.get(i - 1).x, points.get(i - 1).y, points.get(i).x, points.get(i).y);
System.out.println("Dist : " + f.getP1().distance(f.getP2()));
// g.draw(f);
}
g.fill(new Ellipse2D.Float(points.get(i).x - 0.8f, points.get(i).y - 0.8f, 1.6f, 1.6f));
}
g.setColor(Color.BLUE);
g.fill(new Ellipse2D.Float(pFrom.x - 1, pFrom.y - 1, 3, 3));
g.fill(new Ellipse2D.Float(pTo.x - 1, pTo.y - 1, 3, 3));
g.dispose();
ImageIO.write(img, "PNG", new File("result.png"));
}
static class PointF
{
public float x, y;
public PointF(float x, float y)
{
this.x = x;
this.y = y;
}
#Override
public String toString()
{
return "(" + x + "," + y + ")";
}
}
private static List<PointF> generateCurve(PointF pFrom, PointF pTo, float pRadius, float pMinDistance, boolean shortest, boolean side)
{
List<PointF> pOutPut = new ArrayList<PointF>();
// Calculate the middle of the two given points.
PointF mPoint = new PointF(pFrom.x + pTo.x, pFrom.y + pTo.y);
mPoint.x /= 2.0f;
mPoint.y /= 2.0f;
System.out.println("Middle Between From and To = " + mPoint);
// Calculate the distance between the two points
float xDiff = pTo.x - pFrom.x;
float yDiff = pTo.y - pFrom.y;
float distance = (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
System.out.println("Distance between From and To = " + distance);
if (pRadius * 2.0f < distance) {
throw new IllegalArgumentException("The radius is too small! The given points wont fall on the circle.");
}
// Calculate the middle of the expected curve.
float factor = (float) Math.sqrt((pRadius * pRadius) / ((pTo.x - pFrom.x) * (pTo.x - pFrom.x) + (pTo.y - pFrom.y) * (pTo.y - pFrom.y)) - 0.25f);
PointF circleMiddlePoint = new PointF(0, 0);
if (side) {
circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) + factor * (pTo.y - pFrom.y);
circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) + factor * (pFrom.x - pTo.x);
} else {
circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) - factor * (pTo.y - pFrom.y);
circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) - factor * (pFrom.x - pTo.x);
}
System.out.println("Middle = " + circleMiddlePoint);
// Calculate the two reference angles
float angle1 = (float) Math.atan2(pFrom.y - circleMiddlePoint.y, pFrom.x - circleMiddlePoint.x);
float angle2 = (float) Math.atan2(pTo.y - circleMiddlePoint.y, pTo.x - circleMiddlePoint.x);
// Calculate the step.
float step = pMinDistance / pRadius;
System.out.println("Step = " + step);
// Swap them if needed
if (angle1 > angle2) {
float temp = angle1;
angle1 = angle2;
angle2 = temp;
}
boolean flipped = false;
if (!shortest) {
if (angle2 - angle1 < Math.PI) {
float temp = angle1;
angle1 = angle2;
angle2 = temp;
angle2 += Math.PI * 2.0f;
flipped = true;
}
}
for (float f = angle1; f < angle2; f += step) {
PointF p = new PointF((float) Math.cos(f) * pRadius + circleMiddlePoint.x, (float) Math.sin(f) * pRadius + circleMiddlePoint.y);
pOutPut.add(p);
}
if (flipped ^ side) {
pOutPut.add(pFrom);
} else {
pOutPut.add(pTo);
}
return pOutPut;
}
}
and the use the generateCurve method like this to have a curve between the from and to points..
generateCurve(pFrom, pTo, 100f, 7f, true, false);
Okay, here it is, testing and working. The problems were based on the fact that I don't use graphics much, so I have to remind myself that the coordinate systems are backward, and on the fact that the Javadoc description of the Arc2D constructor is atrocious.
In addition to these, I found that your point creation (for the two points to be connected) was extremely inefficient given the requirements. I had assumed you actually had to receive two arbitrary points and then calculate their angles, etc., but based on what you put on Pastebin, we can define the two points however we please. This benefits us.
Anyway, here's a working version, with none of that gobbledegook from before. Simplified code is simplified:
import javax.swing.JComponent;
import java.awt.geom.*;
import java.awt.*;
import java.util.*;
public class Canvas extends JComponent {
double circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2;
Random random = new Random();
double distance;
private static double theta1;
private static double theta2;
private static double theta;
// private static double radius;
private Point2D point1;
private Point2D point2;
private Point2D center;
private static int direction;
private static final int CW = -1;
private static final int CCW = 1;
public Canvas() {
/*
* You want two random points on a circle, so let's start correctly,
* by setting a random *radius*, and then two random *angles*.
*
* This has the added benefit of giving us the angles without having to calculate them
*/
radius = random.nextInt(175); //your maximum radius is higher, but we only have 200 pixels in each cardinal direction
theta1 = random.nextInt(360); //angle to first point (absolute measurement)
theta2 = random.nextInt(360); //angle to second point
//build the points
center = new Point2D.Double(200, 200); //your frame is actually 400 pixels on a side
point1 = new Point2D.Double(radius * Math.cos(toRadians(theta1)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta1)));
point2 = new Point2D.Double(radius * Math.cos(toRadians(theta2)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta2)));
theta = Math.abs(theta1 - theta2) <= 180 ? Math.abs(theta1 - theta2) : 360 - (Math.abs(theta1 - theta2));
if ((theta1 + theta) % 360 == theta2) {
direction = CCW;
} else {
direction = CW;
}
System.out.println("theta1: " + theta1 + "; theta2: " + theta2 + "; theta: " + theta + "; direction: " + (direction == CCW ? "CCW" : "CW"));
System.out.println("point1: (" + (point1.getX() - center.getX()) + ", " + (center.getY() - point1.getY()) + ")");
System.out.println("point2: (" + (point2.getX() - center.getX()) + ", " + (center.getY() - point2.getY()) + ")");
// Radius now equal for both points.
}
public double toRadians(double angle) {
return angle * Math.PI / 180;
}
public double toDegrees(double angle) {
return angle * 180 / Math.PI;
}
public void paintComponent(Graphics g2) {
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL));
//centerpoint should be based on the actual center point
Arc2D.Double centerPoint = new Arc2D.Double(center.getX() - 2, center.getY() - 2, 4, 4, 0,
360, Arc2D.OPEN);
//likewise these points
Arc2D.Double point11 = new Arc2D.Double(point1.getX() - 2, point1.getY() - 2, 4, 4, 0, 360,
Arc2D.OPEN);
Arc2D.Double point22 = new Arc2D.Double(point2.getX() - 2, point2.getY() - 2, 4, 4, 0, 360,
Arc2D.OPEN);
// 3 points drawn in black
g.setColor(Color.BLACK);
g.draw(centerPoint);
g.draw(point11);
g.draw(point22);
// Arc drawn in blue
g.setColor(Color.BLUE);
g.draw(new Arc2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius, theta1, theta * direction, Arc2D.OPEN));
}
}

How do I draw an arrowhead (in Android)?

I'm fairly new to Android and have been toying around with Canvas. I'm attempting to draw an arrow but I'm only having luck with drawing the shaft, none of the arrowhead is working.
I have searched a bit and found a Java example, but Android doesn't have GeneralPath or AffineTransform.
Right now my code looks like the following (the arrowhead looks nothing like an arrowhead):
public class DrawableView extends View {
Context mContext;
private int centerX;
private int centerY;
private int radius;
private double arrLength;
private double arrHeading;
private int margin = 10;
public DrawableView(Context context) {
super(context);
mContext = context;
}
#Override
protected void onDraw(Canvas canvas) {
//Paint Background
Paint background = new Paint();
background.setColor(getResources().getColor(R.color.background);
canvas.drawRect(0, 0, getWidth(), getHeight(), background);
//Set vars for Arrow Paint
Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.arrowColor);
centerX = getWidth() / 2;
centerY = getHeight() / 2;
arrLength = radius - 10;
if(centerX < centerY)
radius = centerX - margin;
else
radius = centerY - margin;
//Draw Shaft
int[] xy = findArrowPos(arrLength, arrHeading);
canvas.drawLine(centerX, centerY, xy[0], xy[1], paint);
//Draw ArrowHead
//This is where I'm confused
}
private int[] findArrowPos(double length, double angle) {
int[] points = new int[2];
double theta = Math.toRadians(angle);
points[0] = centerX + (int) (length * Math.cos(theta));
points[1] = centerY + (int) (length * Math.sin(theta));
return points;
}
}
I have taken a look at the following threads for guidance:
* http://www.java-forums.org/awt-swing/6241-how-u-rotate-arrow-mark-line-moves-accordingly.html
* How to draw a directed arrow line in Java?
How about using "Path myPath = new Path();" where you would give the x and y positions to create a triangle using lines and filling it. You can read about it, here is an example I took from somewhere.
// create and draw triangles
// use a Path object to store the 3 line segments
// use .offset to draw in many locations
// note: this triangle is not centered at 0,0
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(Color.RED);
Path path = new Path();
path.moveTo(0, -10);
path.lineTo(5, 0);
path.lineTo(-5, 0);
path.close();
path.offset(10, 40);
canvas.drawPath(path, paint);
path.offset(50, 100);
canvas.drawPath(path, paint);
// offset is cumlative
// next draw displaces 50,100 from previous
path.offset(50, 100);
canvas.drawPath(path, paint);
My Arrow Drawing code, maybe it can be of some use for somebody:
/**
* Draw an arrow
* change internal radius and angle to change appearance
* - angle : angle in degrees of the arrows legs
* - radius : length of the arrows legs
* #author Steven Roelants 2017
*
* #param paint
* #param canvas
* #param from_x
* #param from_y
* #param to_x
* #param to_y
*/
private void drawArrow(Paint paint, Canvas canvas, float from_x, float from_y, float to_x, float to_y)
{
float angle,anglerad, radius, lineangle;
//values to change for other appearance *CHANGE THESE FOR OTHER SIZE ARROWHEADS*
radius=10;
angle=15;
//some angle calculations
anglerad= (float) (PI*angle/180.0f);
lineangle= (float) (atan2(to_y-from_y,to_x-from_x));
//tha line
canvas.drawLine(from_x,from_y,to_x,to_y,paint);
//tha triangle
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(to_x, to_y);
path.lineTo((float)(to_x-radius*cos(lineangle - (anglerad / 2.0))),
(float)(to_y-radius*sin(lineangle - (anglerad / 2.0))));
path.lineTo((float)(to_x-radius*cos(lineangle + (anglerad / 2.0))),
(float)(to_y-radius*sin(lineangle + (anglerad / 2.0))));
path.close();
canvas.drawPath(path, paint);
}
I try this code it has been working perfectly:
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
startPoint = new PointF(event.getX(), event.getY());
endPoint = new PointF();
invalidate();
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(x - mX);
System.out.println("action move");
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE)
{
// currentDrawingPath.path.quadTo(mX,mY,(x + mX)/2, (y + mY)/2);
}
mX = x;
mY = y;
endPoint.x = event.getX();
endPoint.y = event.getY();
isDrawing = true;
invalidate();
break;
case MotionEvent.ACTION_UP:
mPath.lineTo(mX, mY);
float deltaX = endPoint.x-startPoint.x;
float deltaY = endPoint.y-startPoint.y;
float frac = (float) 0.1;
float point_x_1 = startPoint.x + (float) ((1 - frac) * deltaX + frac * deltaY);
float point_y_1 = startPoint.y + (float) ((1 - frac) * deltaY - frac * deltaX);
float point_x_2 = endPoint.x;
float point_y_2 = endPoint.y;
float point_x_3 = startPoint.x + (float) ((1 - frac) * deltaX - frac * deltaY);
float point_y_3 = startPoint.y + (float) ((1 - frac) * deltaY + frac * deltaX);
mPath.moveTo(point_x_1, point_y_1);
mPath.lineTo(point_x_2, point_y_2);
mPath.lineTo(point_x_3, point_y_3);
mPath.lineTo(point_x_1, point_y_1);
mPath.lineTo(point_x_1, point_y_1);
mCanvas.drawPath(mPath, ppaint);
endPoint.x = event.getX();
endPoint.y = event.getY();
isDrawing = false;
invalidate();
break;
default:
break;
}
I've been having the same problem, I need an arrow to point in a certain direction. After playing around with drawing algorithms I decided the simplest method is to use a bitmap & simply use a Matrix to rotate it, e.g.
ImageView image = (ImageView) findViewById(R.id.bitmap_image);
Bitmap bMap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
Matrix mat = new Matrix();
mat.postRotate(90);
Bitmap bMapRotate = Bitmap.createBitmap(bMap, 0, 0, bMap.getWidth(), bMap.getHeight(), mat, true);
image.setImageBitmap(bMapRotate);
then your bitmap can be any fancy looking arrow you like.
If you are looking for the solution to draw thousands of arrows under a second, with fixed length head lines, try this function (draws only arrow heads):
private void fillArrow(Paint paint, Canvas canvas, float x0, float y0, float x1, float y1) {
paint.setStyle(Paint.Style.STROKE);
int arrowHeadLenght = 10;
int arrowHeadAngle = 45;
float[] linePts = new float[] {x1 - arrowHeadLenght, y1, x1, y1};
float[] linePts2 = new float[] {x1, y1, x1, y1 + arrowHeadLenght};
Matrix rotateMat = new Matrix();
//get the center of the line
float centerX = x1;
float centerY = y1;
//set the angle
double angle = Math.atan2(y1 - y0, x1 - x0) * 180 / Math.PI + arrowHeadAngle;
//rotate the matrix around the center
rotateMat.setRotate((float) angle, centerX, centerY);
rotateMat.mapPoints(linePts);
rotateMat.mapPoints(linePts2);
canvas.drawLine(linePts [0], linePts [1], linePts [2], linePts [3], paint);
canvas.drawLine(linePts2 [0], linePts2 [1], linePts2 [2], linePts2 [3], paint);
}
Based on https://gamedev.stackexchange.com/questions/44456/drawing-lines-on-android-with-matrix
Here is code working perfect for me draw arrow head while drawing line on canvas
package com.example.canvasexample;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
public class DrawerViewArrow extends View {
private ArrayList<Path> drawingLinePath;
private ArrayList<Path> drawingArrowPath;
private ArrayList<Paint> drawingLinePaint;
private int pathIndex = 0;
private float startX = -1, startY = -1;
private float mX = -1, mY = -1;
public int arrowLength = 80;
public int arrowWidth = 45;
public int strokeWidth = 10;
public DrawerViewArrow(Context context) {
super(context);
initPath();
}
public DrawerViewArrow(Context context, #NonNull AttributeSet attrs) {
super(context, attrs);
initPath();
}
public DrawerViewArrow(Context context, #NonNull AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPath();
}
private Paint initPaint() {
Paint mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.GREEN);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(strokeWidth);
return mPaint;
}
private void initPath() {
drawingLinePath = new ArrayList<>();
drawingArrowPath = new ArrayList<>();
drawingLinePath.add(new Path());
drawingArrowPath.add(new Path());
drawingLinePaint = new ArrayList<>();
drawingLinePaint.add(initPaint());
pathIndex++;
}
private Path createPath(MotionEvent event) {
Path path = new Path();
path.moveTo(event.getX(), event.getY());
return path;
}
private void updateIndex(MotionEvent event) {
if (pathIndex == drawingLinePath.size()) {
drawingLinePath.add(createPath(event));
drawingArrowPath.add(createPath(event));
drawingLinePaint.add(initPaint());
pathIndex++;
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (startX > -1 && mX > -1) {
canvas.drawLine(startX, startY, mX, mY, initPaint());
drawArrow(canvas);
}
for (int index = 0; index < pathIndex; index++) {
Path path = drawingLinePath.get(index);
Path arrow_path = drawingArrowPath.get(index);
Paint paint = drawingLinePaint.get(index);
canvas.drawPath(path, paint);
canvas.drawPath(arrow_path, paint);
}
}
private void drawArrow(Canvas canvas) {
double angle = calculateAngle(startX, startY, mX, mY);
float final_angle = (float) (180 - angle);
Path arrow_path = new Path();
Matrix arrow_matrix = new Matrix();
arrow_matrix.postRotate(final_angle, mX, mY);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX - arrowWidth, mY + arrowLength);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX + arrowWidth, mY + arrowLength);
arrow_path.lineTo(mX - (arrowWidth), mY + arrowLength);
arrow_path.transform(arrow_matrix);
canvas.drawPath(arrow_path, initPaint());
}
private void saveArrow() {
if (mX == -1 || mY == -1) {
return;
}
double angle = calculateAngle(startX, startY, mX, mY);
float final_angle = (float) (180 - angle);
Path arrow_path = drawingArrowPath.get(pathIndex - 1);
Matrix arrow_matrix = new Matrix();
arrow_matrix.postRotate(final_angle, mX, mY);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX - arrowWidth, mY + arrowLength);
arrow_path.moveTo(mX, mY);
arrow_path.lineTo(mX + arrowWidth, mY + arrowLength);
arrow_path.lineTo(mX - (arrowWidth), mY + arrowLength);
arrow_path.transform(arrow_matrix);
}
public double calculateAngle(double x1, double y1, double x2, double y2) {
double angle = Math.toDegrees(Math.atan2(x2 - x1, y2 - y1));
angle = angle + Math.ceil(-angle / 360) * 360; //Keep angle between 0 and 360
return angle;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case ACTION_UP:
actionUp(event);
break;
case ACTION_MOVE:
actionMove(event);
break;
case ACTION_DOWN:
actionDown(event);
break;
}
invalidate();
return true;
}
private void actionDown(MotionEvent event) {
updateIndex(event);
startX = event.getX();
startY = event.getY();
}
private void actionMove(MotionEvent event) {
mX = event.getX();
mY = event.getY();
}
private void actionUp(MotionEvent event) {
drawingLinePath.get(pathIndex - 1).lineTo(event.getX(), event.getY());
saveArrow();
startX = -1;
startY = -1;
mX = -1;
mY = -1;
}
}
Use a Path as below and adjust the co-ordinates accordingly:
// Construct a wedge-shaped path
Path mPath = new Path();
mPath.moveTo(0, -50);
mPath.lineTo(-20, 60);
mPath.lineTo(0, 50);
mPath.lineTo(20, 60);
mPath.close();
Copypast from this answer https://stackoverflow.com/a/29383352/9975029
private void fillArrow(Canvas canvas, float x0, float y0, float x1, float y1) {
paint.setStyle(Paint.Style.FILL);
float deltaX = x1 - x0;
float deltaY = y1 - y0;
double distance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
float frac = (float) (1 / (distance / 30));
float point_x_1 = x0 + (float) ((1 - frac) * deltaX + frac * deltaY);
float point_y_1 = y0 + (float) ((1 - frac) * deltaY - frac * deltaX);
float point_x_2 = x1;
float point_y_2 = y1;
float point_x_3 = x0 + (float) ((1 - frac) * deltaX - frac * deltaY);
float point_y_3 = y0 + (float) ((1 - frac) * deltaY + frac * deltaX);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(point_x_1, point_y_1);
path.lineTo(point_x_2, point_y_2);
path.lineTo(point_x_3, point_y_3);
path.lineTo(point_x_1, point_y_1);
path.lineTo(point_x_1, point_y_1);
path.close();
canvas.drawPath(path, paint);
}
Here's my arrow drawing code without using trig functions explicitly (although the underlying math obviously uses trig)
The math makes an arrow head like half of a square (cut diagonally) where variable L is the length of the diagonal. Also, the arrow ends at point p2 which means that for small difference between p2 and p1, the arrow head will be drawn 'before' p2 for sufficient L. Also if p1 and p2 are the same, the arrow will not be drawn because the math would cause division by zero.
I suggest you use Paint.Style.FILL_AND_STROKE to draw this arrow.
I'm open for any questions.
void drawArrow(Canvas canvas, Point p1, Point p2, float L) {
float fsin, fcos;
double d;
if(p1.equals(p2))
return;
d = Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
fsin = (p2.y - p1.y)/(float)d;
fcos = (p2.x - p1.x)/(float)d;
PointF p3 = new PointF(p2.x - L/2*(fsin + fcos), p2.y + L/2*(fcos - fsin));
PointF p4 = new PointF(p2.x + L/2*(fsin - fcos), p2.y - L/2*(fsin + fcos));
canvas.drawLine(p1.x, p1.y, p2.x, p2.y, arrowPaint);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(p2.x, p2.y);
path.lineTo(p3.x, p3.y);
path.lineTo(p4.x, p4.y);
path.close();
canvas.drawPath(path, arrowPaint);
}

Categories

Resources