使用 Java AWT 在圆圈中绘制文本(相应方向的字母)
Draw text in circle with Java AWT (with the letters oriented accordingly)
我正在尝试使用 Java AWT 和 AffineTranform
在圆圈中绘制给定的字符串,其中字母也会沿着圆圈上下颠倒。
我从 the following program 中的代码开始,将文本单独绘制成一条曲线。
我还使用了 a snippet I found here 的坐标计算来绘制模拟时钟的数字。
下面是我的代码。老实说,我不是 100% 理解这些方法是如何修复我的代码的。在 trial-and-error 尝试中,我一直在摆弄 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);
// 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);
}
}
}
这是当前输出:
我还注意到,如果我在 theta
公式中使用 (2 * length)
而不是 (length + 1)
,则字符串的前半部分似乎位于正确的位置,但没有倾斜方向正确(字符“6”侧向/旋转 90 度,而不是倒置/旋转 180 度):
正如我提到的,我真的不知道 AffineTransform
在给定坐标和 theta 方面是如何工作的。对此的解释将不胜感激,如果有人可以帮助我修复代码,则更是如此。
另请注意,我想为可变长度的字符串实现此公式。我现在 hard-coded 到 "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
:
但是另外两个呢?
EDIT2:我误读了一小部分 @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);
尽管我仍然发现较大的输入字符串存在一些小问题,所以会调查一下。
EDIT3:已经有一段时间了,但我刚刚回到这个问题上,我发现了我很容易尝试的长度为 66 的测试用例的错误:360
应该是 360d
,因为 360/length
会使用 integer-division,否则如果 360 不能被长度整除。
我现在有了这个,它可以按预期工作任何长度。请注意,中心并不完全正确,@Mbo 提供的答案可以提供帮助。我唯一的目标是制作文本圈(长度为 66)。它在屏幕上的位置和大小并不是那么重要。
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);
您的位置初始角度为 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
- 旋转
- 移动到最终位置(相对于零,然后移动圆心)
我正在尝试使用 Java AWT 和 AffineTranform
在圆圈中绘制给定的字符串,其中字母也会沿着圆圈上下颠倒。
我从 the following program 中的代码开始,将文本单独绘制成一条曲线。
我还使用了 a snippet I found here 的坐标计算来绘制模拟时钟的数字。
下面是我的代码。老实说,我不是 100% 理解这些方法是如何修复我的代码的。在 trial-and-error 尝试中,我一直在摆弄 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);
// 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);
}
}
}
这是当前输出:
我还注意到,如果我在 theta
公式中使用 (2 * length)
而不是 (length + 1)
,则字符串的前半部分似乎位于正确的位置,但没有倾斜方向正确(字符“6”侧向/旋转 90 度,而不是倒置/旋转 180 度):
正如我提到的,我真的不知道 AffineTransform
在给定坐标和 theta 方面是如何工作的。对此的解释将不胜感激,如果有人可以帮助我修复代码,则更是如此。
另请注意,我想为可变长度的字符串实现此公式。我现在 hard-coded 到 "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
:
但是另外两个呢?
EDIT2:我误读了一小部分 @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);
尽管我仍然发现较大的输入字符串存在一些小问题,所以会调查一下。
EDIT3:已经有一段时间了,但我刚刚回到这个问题上,我发现了我很容易尝试的长度为 66 的测试用例的错误:360
应该是 360d
,因为 360/length
会使用 integer-division,否则如果 360 不能被长度整除。
我现在有了这个,它可以按预期工作任何长度。请注意,中心并不完全正确,@Mbo 提供的答案可以提供帮助。我唯一的目标是制作文本圈(长度为 66)。它在屏幕上的位置和大小并不是那么重要。
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);
您的位置初始角度为 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
- 旋转
- 移动到最终位置(相对于零,然后移动圆心)