【发布时间】:2020-08-07 12:52:59
【问题描述】:
我正在尝试使用带有AffineTranform 的Java AWT 将给定的字符串绘制成一个圆圈,其中字母也会沿着圆圈倒置。
我从the following program 的代码开始单独绘制一条曲线。
我还使用a snippet I found here 的坐标计算来绘制模拟时钟的数字。
下面是我的代码。老实说,我不能 100% 理解这些方法如何工作以修复我的代码。在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 在给定坐标和θ 方面是如何工作的。对此的解释将不胜感激,如果有人可以帮助我修复代码,则更是如此。
另请注意,我想为可变长度的字符串实现此公式。我现在已将其硬编码为 "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 会如果 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);
【问题讨论】:
标签: java text geometry awt affinetransform