AffineTransform без преобразования Stroke?
При использовании Graphics2D scale()
функция с двумя различными параметрами (масштабирование с различными соотношениями в направлении x и y), все, что нарисовано позже в этом объекте Graphics2D, также масштабируется. Это имеет странный эффект, что линии, нарисованные в одном направлении, толще, чем в другом направлении. Следующая программа производит этот эффект, она показывает это окно:
public class StrokeExample extends JPanel {
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
g.setStroke(new BasicStroke(0.2f));
int height = getHeight();
int width = getWidth();
g.scale(width/7.0, height/4.0);
g.setColor(Color.BLACK);
g.draw(new Rectangle( 2, 1, 4, 2));
}
public static void main(String[] params) {
EventQueue.invokeLater(new Runnable(){public void run() {
StrokeExample example = new StrokeExample();
JFrame f = new JFrame("StrokeExample");
f.setSize(100, 300);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(example);
f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
f.setVisible(true);
}});
}
}
Я использую это преобразование координат, чтобы избежать необходимости вручную преобразовывать координаты модели моего приложения ((2,1, 2,4) в этом примере) в экранные (или компонентные) пиксельные координаты, но я не хочу этого искажения штрихов, Другими словами, я хочу, чтобы все линии имели одинаковую ширину, независимо от текущих x- и y-масштабных коэффициентов.
Я знаю, что вызывает этот эффект (объект Stroke создает заштрихованную форму прямоугольника, который должен быть нарисован в пользовательских координатах, которые затем переводятся в экранные координаты), но я не уверен, как это решить.
- Должен ли я создать новую реализацию Stroke, которая по-разному обводит фигуры в направлении X и Y (тем самым устраняя искажение здесь)? (Или кто-нибудь уже знает такую реализацию?)
- Должен ли я преобразовать свои фигуры в координаты экрана и обводить их там?
- Любые другие (лучшие) идеи?
3 ответа
Оказывается, мой вопрос не был настолько ужасно трудным, и что две мои идеи, приведенные в вопросе, на самом деле являются одной и той же идеей. Вот TransformedStroke
класс, который реализует искаженный Stroke
путем преобразования Shape
,
import java.awt.*;
import java.awt.geom.*;
/**
* A implementation of {@link Stroke} which transforms another Stroke
* with an {@link AffineTransform} before stroking with it.
*
* This class is immutable as long as the underlying stroke is
* immutable.
*/
public class TransformedStroke
implements Stroke
{
/**
* To make this serializable without problems.
*/
private static final long serialVersionUID = 1;
/**
* the AffineTransform used to transform the shape before stroking.
*/
private AffineTransform transform;
/**
* The inverse of {@link #transform}, used to transform
* back after stroking.
*/
private AffineTransform inverse;
/**
* Our base stroke.
*/
private Stroke stroke;
/**
* Creates a TransformedStroke based on another Stroke
* and an AffineTransform.
*/
public TransformedStroke(Stroke base, AffineTransform at)
throws NoninvertibleTransformException
{
this.transform = new AffineTransform(at);
this.inverse = transform.createInverse();
this.stroke = base;
}
/**
* Strokes the given Shape with this stroke, creating an outline.
*
* This outline is distorted by our AffineTransform relative to the
* outline which would be given by the base stroke, but only in terms
* of scaling (i.e. thickness of the lines), as translation and rotation
* are undone after the stroking.
*/
public Shape createStrokedShape(Shape s) {
Shape sTrans = transform.createTransformedShape(s);
Shape sTransStroked = stroke.createStrokedShape(sTrans);
Shape sStroked = inverse.createTransformedShape(sTransStroked);
return sStroked;
}
}
Мой метод рисования, использующий его, выглядит так:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
int height = getHeight();
int width = getWidth();
g.scale(width/4.0, height/7.0);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
g.getTransform()));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
Тогда мое окно выглядит так:
Я вполне доволен этим, но если у кого-то есть больше идей, тем не менее, не стесняйтесь отвечать.
Внимание: это g.getTransform()
возвращает полное преобразование g относительно пространства устройства, а не только преобразование, примененное после .create()
, Так что, если кто-то сделал какое-то масштабирование перед тем, как передать Графику моему компоненту, это все равно рисовало бы с штрихом шириной в 2 устройства, а не 2 пикселя от графики, предоставленной моему методу. Если это будет проблемой, используйте это так:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
AffineTransform trans = new AffineTransform();
int height = getHeight();
int width = getWidth();
trans.scale(width/4.0, height/7.0);
g.transform(trans);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
trans));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
В Swing обычно ваша графика передается paintComponent
переводится только (так что (0,0) - верхний левый угол вашего компонента), не масштабируется, поэтому нет никакой разницы.
Существует более простое и менее "хакерское" решение, чем оригинальное TransformedStroke
ответ.
Я понял, когда прочитал, как работает конвейер рендеринга:
(из http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)
- Если
Shape
должен быть поглажен,Stroke
атрибут вGraphics2D
контекст используется для генерации новогоShape
это охватывает пройденный путь.- Координаты
Shape
пути преобразуются из пространства пользователя в пространство устройства в соответствии с атрибутом transform вGraphics2D
контекст.Shape
путь обрезается с помощью атрибута клипа вGraphics2D
контекст.- Остальные
Shape
, если таковые имеются, заполняется с использованиемPaint
а такжеComposite
атрибуты вGraphics2D
контекст.
В идеале вы и я ищем способ поменять первые два шага.
Если вы внимательно посмотрите на второй шаг, TransformedStroke
уже содержит часть решения.
Shape sTrans = transform.createTransformedShape(s);
решение
Вместо:
g.scale(
...)
, g.transform(
...)
, без разницы,g.draw(new Rectangle( 1, 2, 2, 4));
Или, используя TransformedStroke
:
g.setStroke(new TransformedStroke(new BasicStroke(2f), g.getTransform());
g.draw(new Rectangle( 1, 2, 2, 4));
Я предлагаю вам сделать:
transform =
без разницы,g.draw(transform.createTransformedShape(new Rectangle( 1, 2, 2, 4));
Не трансформируйся g
больше. Когда-либо. Вместо этого преобразуйте фигуры, используя преобразование, которое вы делаете и изменяете сами.
обсуждение
TransformedStroke
больше похоже на "взлом", чем на то, как авторы Stroke
подразумевал интерфейс, который будет использоваться. Это также требует дополнительного класса.
Это решение держит отдельный Transform
вокруг и модифицирует Shape
вместо преобразования Graphics
объект. Это, однако, ни в коем случае не хак, потому что я не злоупотребляю существующей функциональностью, а использую функциональность API именно так, как она должна использоваться. Я просто использую более явные части API вместо методов API "ярлык" / "удобство" (g.scale()
так далее.).
С точки зрения производительности это решение может быть только более эффективным. Фактически один шаг теперь пропущен. В оригинальном решении, TransformedStroke
трансформирует фигуру дважды и гладит фигуру один раз. Это решение явно преобразует фигуру, а *current* штрих обводит фигуру один раз.
Вы только что пытались увеличить int и int в приложении, например, int x = 500, int y = 900??? Также я предлагаю, чтобы без переписывания весь код был реализован так, чтобы рэки были толще, когда приложение ближе друг к другу, как удвоение прямоугольника сверху и снизу, но когда приложение расширяется, рэки сверху и снизу идут вернуться к нормальной жизни...