英文:
Draw text in circle with Java AWT (with the letters oriented accordingly)
问题
我正在尝试使用Java AWT和AffineTransform
来在一个圆上绘制给定的字符串,其中字母也会沿着圆上下颠倒。
我从以下程序的代码开始绘制文本沿着曲线:这里。我还使用了从这里找到的坐标计算方法来绘制模拟时钟的数字:这里。
以下是我的代码。坦率地说,我并不完全了解这些方法如何工作,以便修复我的代码。我试过在coords
和theta
值上进行试验和错误尝试。
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Panel;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
public class Main extends Panel {
public static void main(String[] args){
Frame f = new Frame("Circle Text");
f.add(new Main());
f.setSize(750, 750);
f.setVisible(true);
}
private int[] getPointXY(int dist, double rad){
int[] coord = new int[2];
coord[0] = (int) (dist * Math.cos(rad) + dist);
coord[1] = (int) (-dist * Math.sin(rad) + dist);
return coord;
}
@Override
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// 现在是硬编码,使用12个字符表示30度角(类似时钟)
String text = "0123456789AB";
Font font = new Font("Serif", 0, 25);
FontRenderContext frc = g2.getFontRenderContext();
g2.translate(200, 200); // 文本的起始位置
GlyphVector gv = font.createGlyphVector(frc, text);
int length = gv.getNumGlyphs(); // 与text.length()相同
final double toRad = Math.PI / 180;
for(int i = 0; i < length; i++){
//Point2D p = gv.getGlyphPosition(i);
int[] coords = getPointXY(100, -360 / length * i * toRad + Math.PI / 2);
double theta = 2 * Math.PI / (length + 1) * i;
AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
at.rotate(theta);
Shape glyph = gv.getGlyphOutline(i);
Shape transformedGlyph = at.createTransformedShape(glyph);
g2.fill(transformedGlyph);
}
}
}
这是当前的输出:
我还注意到,如果在theta
公式中使用(2 * length)
而不是(length + 1)
,则字符串的前半部分似乎处于正确的位置,除了角度不正确(字符'6'是横向/旋转90度,而不是颠倒/旋转180度):
正如我提到的,我并不真正了解AffineTransform
如何工作,特别是关于给定坐标和theta的部分。关于这一点的解释将不胜感激,如果有人能帮助我修复代码就更好了。还请注意,我想要为可变长度的字符串实现这个公式。我现在已经硬编码成了"0123456789AB"
(12个字符,类似于30度步进的时钟),但它也应该适用于比如包含8个字符或66个字符的字符串。
编辑:根据@MBo的建议,我对代码进行了以下修改:
int r = 50;
int[] coords = getPointXY(r, -360 / length * i * toRad + Math.PI / 2);
gv.setGlyphPosition(i, new Point(coords[0], coords[1]));
final AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
at.rotate(-2 * Math.PI * i / length);
at.translate(r * Math.cos(Math.PI / 2 - 2 * Math.PI * i / length),
r * Math.sin(Math.PI / 2 - 2 * Math.PI * i / length));
Shape glyph = gv.getGlyphOutline(i);
Shape transformedGlyph = at.createTransformedShape(glyph);
g2.fill(transformedGlyph);
现在我有了一个圆圈,所以这是一些进展,但不幸的是仍然存在三个问题:
- 第一个字符的起始位置在大约4点钟的位置而不是在顶部。
- 字符没有正确倾斜,其顶部朝向圆的中心。
- 字符串是逆时针绘制的,而不是顺时针绘制的。
最后一个问题很容易解决,只需将旋转中的-2
改为2
:
但其他两个问题呢?
编辑2:我误读了@MBo的答案中关于初始字形设置的小节。现在它已经可以工作了。以下是与上述编辑相比的代码更改:
gv.setGlyphPosition(i, new Point(-length / 2, -length / 2));
AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
at.rotate(2 * Math.PI * i / length);
尽管对于较大的输入字符串仍然存在一些小问题,所以我会进一步研究。
编辑3:已经过了一段时间,但我刚刚回到这个问题,我很容易地发现了我对于长度为66的测试用例所犯的错误:360
应该是360d
,因为如果360不能被长度整除
英文:
I'm trying to use Java AWT with AffineTranform
to draw a given String in a circle, where the letters would also go upside down along the circle.
I started with the code from the following program to draw text alone a curve.
I've also used the calculation of the coordinates from a snippet I found here for drawing the numbers of an analog clock.
Below is my code. To be completely honest, I don't understand 100% how these methods work in order to fix my code. I've been fiddling around a bit in a trial-and-error attempt with the coords
and theta
values.
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Panel;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
public class Main extends Panel {
public static void main(String[] args){
Frame f = new Frame("Circle Text");
f.add(new Main());
f.setSize(750, 750);
f.setVisible(true);
}
private int[] getPointXY(int dist, double rad){
int[] coord = new int[2];
coord[0] = (int) (dist * Math.cos(rad) + dist);
coord[1] = (int) (-dist * Math.sin(rad) + dist);
return coord;
}
@Override
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Hard-coded for now, using 12 characters for 30 degrees angles (like a clock)
String text = "0123456789AB";
Font font = new Font("Serif", 0, 25);
FontRenderContext frc = g2.getFontRenderContext();
g2.translate(200, 200); // Starting position of the text
GlyphVector gv = font.createGlyphVector(frc, text);
int length = gv.getNumGlyphs(); // Same as text.length()
final double toRad = Math.PI / 180;
for(int i = 0; i < length; i++){
//Point2D p = gv.getGlyphPosition(i);
int[] coords = getPointXY(100, -360 / length * i * toRad + Math.PI / 2);
double theta = 2 * Math.PI / (length + 1) * i;
AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
at.rotate(theta);
Shape glyph = gv.getGlyphOutline(i);
Shape transformedGlyph = at.createTransformedShape(glyph);
g2.fill(transformedGlyph);
}
}
}
And this is the current output:
I also noticed that if I use (2 * length)
instead of (length + 1)
in the theta
formula, the first halve of the string seems to be in the correct positions, except not angled properly oriented (the character '6' is sideways / 90 degrees rotated, instead of upside down / 180 degrees rotated):
As I mentioned, I don't really know how the AffineTransform
works regarding the given coordinates and theta. An explanation of that would be greatly appreciated, and even more so if someone could help me fix the code.
Also note that I want to implement this formula for a variable length of the String. I've now hard-coded it to "0123456789AB"
(12 characters, so it's similar to a clock with 30 degrees steps), but it should also work with let's say a String of 8 characters or 66 characters.
EDIT: After the suggestions of @MBo I made the following modifications to the code:
int r = 50;
int[] coords = getPointXY(r, -360 / length * i * toRad + Math.PI / 2);
gv.setGlyphPosition(i, new Point(coords[0], coords[1]));
final AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
at.rotate(-2 * Math.PI * i / length);
at.translate(r * Math.cos(Math.PI / 2 - 2 * Math.PI * i / length),
r * Math.sin(Math.PI / 2 - 2 * Math.PI * i / length));
Shape glyph = gv.getGlyphOutline(i);
Shape transformedGlyph = at.createTransformedShape(glyph);
g2.fill(transformedGlyph);
I now do have a circle, so that's something, but unfortunately still with three issues:
- The starting position of the first character is at ~4 o'clock instead of top.
- The characters aren't correctly angled with their tops towards the center of the circle
- The string is drawn counterclockwise instead of clockwise
The last issue is easily fixed, by changing the -2
to 2
in the rotate:
But the other two?
EDIT2: I misread a small section of @MBo's answer regarding the initial glyph set. It's now working. Here the resulting code changes again in comparison to the Edit above:
gv.setGlyphPosition(i, new Point(-length / 2, -length / 2));
AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
at.rotate(2 * Math.PI * i / length);
Although I still see some minor issues with larger input Strings, so will look into that.
EDIT3: It's been a while, but I just got back to this, and I spotted my mistake for the length 66 test case I tried pretty easily: 360
should be a 360d
, because the 360/length
would use integer-division otherwise if 360 isn't evenly divisible by the length.
I now have this, which works as intended for any length. Note that the center isn't completely correct, for which the answer provided by @Mbo can help. My only goal was to make the circle of text (of length 66). Where it is on the screen and how big wasn't really that important.
int[] coords = this.getPointXY(r, -360.0 / length * i * toRad + Math.PI / 2);
gv.setGlyphPosition(i, new Point(0, 0));
AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
at.rotate(2 * Math.PI * i / length);
at.translate(r * Math.cos(Math.PI / 2 - 2 * Math.PI * i / length),
r * Math.sin(Math.PI / 2 - 2 * Math.PI * i / length));
at.translate(-FONT_SIZE / 2, 0);
答案1
得分: 1
您的初始角度为Pi/2
用于位置和0
用于符号旋转。
为了正确设置旋转和位置,我建议:
- 将符号放置在坐标原点(0,0)处
- 旋转它
-2*Math.PI * i / length
- 通过
r*cos(Math.PI/2 - 2*Math.PI * i / length)
和r*sin(Math.PI/2 - 2*Math.PI * i / length)
进行平移 - 通过圆心坐标进行平移
步骤:
注意 - 先旋转,然后移动。
这种方法可能会产生良好但不完美的结果。为了更好的外观,您可以添加第一步 - 将符号平移到其大小的一半以提供围绕其中心的旋转。所以顺序是:
-
向左移动 -glyphpixelsize/2
-
旋转
-
移动到最终位置(相对于零,然后移动到圆心)
英文:
Your initial angle is Pi/2
for position and 0
for glyph rotation.
To set rotation and position properly, I suggest:
- put glyph in the coordinate origin (0,0)
- rotate it by
-2*Math.PI * i / length
- translate it by
r*cos(Math.PI/2 - 2*Math.PI * i / length)
andr*sin(Math.PI/2 - 2*Math.PI * i / length)
- translate it by circle center coordinates
Steps:
Note - rotate, then shift.
This approach perhaps give good but not perfect result. For better looking you can add the first step - translate glyph by half of it's size to provide rotation about it's center. So sequence:
-
shift by -glyphpixelsize/2
-
rotate
-
shift into final position (relative to zero, then shift by circle center)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论