AffineTransform без преобразования штриха?
При использовании функции Graphics2D scale()
с двумя разными параметрами (масштабирование по разным отношениям в x- и y-направлении) все, нарисованные позже на этом объекте Graphics2D, также масштабируется. Это имеет странный эффект, что линии, проведенные в одном направлении, толще, чем линии в другом направлении. Следующая программа производит этот эффект, он показывает это окно:
![example screenshot]()
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, которая поглаживает Shapes по-разному в X- и Y-направлении (тем самым отменив здесь искажение)? (Или кто-нибудь уже знает такую реализацию?)
- Должен ли я преобразовать свои фигуры в координаты экрана и погладить там?
- Любые другие (лучшие) идеи?
Ответы
Ответ 1
Оказывается, мой вопрос не был настолько ужасным и что мои две идеи, заданные в вопросе, на самом деле суть одна и та же идея. Вот класс 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));
}
Затем мое окно выглядит так:
![screenshot of undistorted stroke]()
Я вполне доволен этим, но если у кого-то есть больше идей, не стесняйтесь отвечать тем не менее.
Внимание: Это 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) - это верхний левый угол вашего компонента), не масштабируется, поэтому нет разницы.
Ответ 2
Существует более простое и менее "хакерское" решение, чем исходный ответ TransformedStroke
.
У меня появилась идея, когда я прочитал, как работает конвейер рендеринга:
(из http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)
- Если
Shape
следует погладить, атрибут Stroke
в контексте Graphics2D
используется для создания нового Shape
, который охватывает положенный путь. - Координаты пути
Shape
s преобразуются из пространства пользователя в пространство устройства в соответствии с атрибутом преобразования в контексте Graphics2D
. - Путь
Shape
s обрезается с использованием атрибута клипа в контексте 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 * обводят форму один раз.
Ответ 3
Вы просто попытались сделать int x и int y в приложении больше, чем int x = 500 int y = 900??? Также мое предложение состоит в том, что без перезаписи весь код предназначен для реализации, когда реплики становятся более толстыми, когда приложение ближе друг к другу, более похожее на удвоение прямоугольника сверху и снизу, но когда приложение расширяется, реквизиты сверху и снизу идут вернуться к нормальной жизни...